diff --git a/crates/oxide_emit/src/class/emit.rs b/crates/oxide_emit/src/class/emit.rs index a5438eb..2251e4f 100644 --- a/crates/oxide_emit/src/class/emit.rs +++ b/crates/oxide_emit/src/class/emit.rs @@ -129,12 +129,7 @@ impl Emitter { // accessor calls (which clear the register file). The synthetic key keeps user // bindings with the same name (outer scopes) untouched. let class_self_cell: Option = ctor_name.as_deref().map(|_| { - let cell_idx = ctx - .captured_bindings - .values() - .copied() - .max() - .map_or(0, |m| m.saturating_add(1)); + let cell_idx = ctx.captured_bindings.values().copied().max().map_or(0, |m| m.saturating_add(1)); ctx.captured_bindings.insert(format!("@@class_self_{cell_idx}"), cell_idx); cell_idx }); @@ -302,9 +297,7 @@ impl Emitter { module.insts.extend(field_ctx.insts); module.constants = field_ctx.constants; module.n_registers = field_ctx.max_regs.max(1); - module - .insts - .push(Inst::new(OpCode::RETURN, Operand::None, Operand::None, Operand::None)); + module.insts.push(Inst::ret(Operand::None, 0, 0)); } module }; @@ -377,10 +370,17 @@ impl Emitter { Operand::None, )); } - self.emit_class_methods(&class.body.body, ctor_reg, proto_reg, &self_binding, class_self_cell, &key_slots, ctx)?; + self.emit_class_methods( + &class.body.body, + ctor_reg, + proto_reg, + &self_binding, + class_self_cell, + &key_slots, + ctx, + )?; self.emit_class_static_elements(&class.body.body, ctor_reg, &key_slots, ctx)?; - // 类构建完成:初始化类名绑定并写入类构造器(类内方法引用该寄存器/捕获)。 if let Some(name) = ctor_name.as_deref() { ctx.init_var(name); diff --git a/crates/oxide_emit/src/emit.rs b/crates/oxide_emit/src/emit.rs index ca1efdd..ee2a401 100644 --- a/crates/oxide_emit/src/emit.rs +++ b/crates/oxide_emit/src/emit.rs @@ -24,7 +24,7 @@ pub fn is_anonymous_function_definition(expr: &oxide_parser::Expression) -> bool } } -use crate::emit_ctx::{LabelCtx, ScopeCtx}; +use crate::emit_ctx::{LabelCtx, LoopEntry, LoopKind, ScopeCtx}; use crate::symbol_table::{Binding, ScopeKind, SymbolTable}; /// 常量池项(bytecode module 类型)re-export,供调用方构造常量。 @@ -144,6 +144,10 @@ pub struct LabelScope { pub(crate) continue_label: Option, /// 标签打开时嵌套的 finally 域数:break/continue 跨越 finally 的计数依据。 pub(crate) finally_depth_at_open: usize, + /// 标签打开时已打开的 for-of 循环数:break/continue 逃出计数依据。 + pub(crate) for_of_depth_at_open: usize, + /// 标签打开时已打开的 for-in 循环数。 + pub(crate) for_in_depth_at_open: usize, } /// 单函数编译上下文:执行流 + 作用域 + 闭包捕获的聚合状态。 @@ -283,6 +287,8 @@ impl CompileCtx { label_scopes: Vec::new(), pending_loop_labels: Vec::new(), finally_depth: 0, + for_of_depth: 0, + for_in_depth: 0, label_counter: 0, }, scopes: ScopeCtx { @@ -456,29 +462,55 @@ impl CompileCtx { id } - pub(crate) fn push_loop(&mut self, break_label: LabelId, continue_label: LabelId) { + pub(crate) fn push_loop(&mut self, break_label: LabelId, continue_label: LabelId, kind: LoopKind) { let fd = self.labels.finally_depth; - self.labels.loop_stack.push((break_label, continue_label, fd)); + // 深度计数先递增再快照:条目记录"打开后(含自身)"的深度,与 + // take_pending_loop_labels 的标签作用域快照一致,逃出计数才能对齐。 + if kind.is_for_of() { + self.labels.for_of_depth += 1; + } + if kind.is_for_in() { + self.labels.for_in_depth += 1; + } + let fod = self.labels.for_of_depth; + let fid = self.labels.for_in_depth; + self.labels.loop_stack.push(LoopEntry { + break_label, + continue_label, + finally_depth_at_open: fd, + for_of_depth_at_open: fod, + for_in_depth_at_open: fid, + kind, + }); } pub(crate) fn pop_loop(&mut self) { - self.labels.loop_stack.pop(); + if let Some(entry) = self.labels.loop_stack.pop() { + if entry.kind.is_for_of() { + self.labels.for_of_depth -= 1; + } + if entry.kind.is_for_in() { + self.labels.for_in_depth -= 1; + } + } } - pub(crate) fn current_loop(&self) -> Option<&(LabelId, LabelId, usize)> { + pub(crate) fn current_loop(&self) -> Option<&LoopEntry> { self.labels.loop_stack.last() } pub(crate) fn push_switch(&mut self, break_label: LabelId) { let fd = self.labels.finally_depth; - self.labels.switch_stack.push((break_label, fd)); + let fod = self.labels.for_of_depth; + let fid = self.labels.for_in_depth; + self.labels.switch_stack.push((break_label, fd, fod, fid)); } pub(crate) fn pop_switch(&mut self) { self.labels.switch_stack.pop(); } - pub(crate) fn current_switch(&self) -> Option<&(LabelId, usize)> { + pub(crate) fn current_switch(&self) -> Option<&(LabelId, usize, usize, usize)> { self.labels.switch_stack.last() } @@ -521,11 +553,15 @@ impl CompileCtx { return Err(format!("SyntaxError: Label '{name}' has already been declared")); } let fd = self.labels.finally_depth; + let fod = self.labels.for_of_depth; + let fid = self.labels.for_in_depth; self.labels.label_scopes.push(LabelScope { name: name.to_string(), break_label, continue_label, finally_depth_at_open: fd, + for_of_depth_at_open: fod, + for_in_depth_at_open: fid, }); Ok(()) } @@ -556,12 +592,16 @@ impl CompileCtx { let names = std::mem::take(&mut self.labels.pending_loop_labels); let count = names.len(); let fd = self.labels.finally_depth; + let fod = self.labels.for_of_depth; + let fid = self.labels.for_in_depth; for name in names { self.labels.label_scopes.push(LabelScope { name, break_label, continue_label: Some(continue_label), finally_depth_at_open: fd, + for_of_depth_at_open: fod, + for_in_depth_at_open: fid, }); } count @@ -627,7 +667,9 @@ impl CompileCtx { /// 组装 IRFunction(两出口共用),take 走编译产物状态。 /// `parent_ctx` 用于补全 upvalue_captures 的 enclosing_reg(父符号表在父 emit 完成后完整)。 - pub(crate) fn assemble_ir(&mut self, param_layout: oxide_ir::ParamLayout, parent_ctx: Option<&CompileCtx>) -> IRFunction { + pub(crate) fn assemble_ir( + &mut self, param_layout: oxide_ir::ParamLayout, parent_ctx: Option<&CompileCtx>, + ) -> IRFunction { let upvalue_captures = self .current_upvalue_captures .iter() @@ -1089,20 +1131,21 @@ impl Emitter { let last_result_reg = self.emit_body_stmts(body_stmts, &mut ctx)?; // 隐式 RETURN:表达式体返回最后表达式,语句体返回 undefined。 + // 函数尾词法上不在任何循环内,迭代器逃出计数恒 0。 if is_expression_body { if let Some(reg) = last_result_reg { - ctx.inst(Inst::new(OpCode::RETURN, Operand::Reg(reg), Operand::None, Operand::None)); + ctx.inst(Inst::ret(Operand::Reg(reg), 0, 0)); } else { let undef_idx = ctx.add_constant(Constant::Undefined); let undef_reg = ctx.alloc_reg(); ctx.inst(Inst::load_const(Operand::Reg(undef_reg), undef_idx)); - ctx.inst(Inst::new(OpCode::RETURN, Operand::Reg(undef_reg), Operand::None, Operand::None)); + ctx.inst(Inst::ret(Operand::Reg(undef_reg), 0, 0)); } } else { let undef_idx = ctx.add_constant(Constant::Undefined); let undef_reg = ctx.alloc_reg(); ctx.inst(Inst::load_const(Operand::Reg(undef_reg), undef_idx)); - ctx.inst(Inst::new(OpCode::RETURN, Operand::Reg(undef_reg), Operand::None, Operand::None)); + ctx.inst(Inst::ret(Operand::Reg(undef_reg), 0, 0)); } // 调用契约参数段只含固定形参:rest 是函数体内普通变量,不在 VM 实参传递区。 diff --git a/crates/oxide_emit/src/emit_ctx.rs b/crates/oxide_emit/src/emit_ctx.rs index 9f9d9f3..1004a00 100644 --- a/crates/oxide_emit/src/emit_ctx.rs +++ b/crates/oxide_emit/src/emit_ctx.rs @@ -10,20 +10,64 @@ use crate::LabelScope; use oxide_ir::operand::LabelId; use oxide_parser::MethodDefinitionKind; +/// 循环语句类别:决定逃出计数是否计入 for-of / for-in 迭代器关闭。 +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub(crate) enum LoopKind { + /// while / do-while / C 风格 for:无迭代器关闭语义。 + Plain, + ForOf, + ForAwaitOf, + ForIn, +} + +impl LoopKind { + /// 是否计入 for-of 逃出关闭计数。for-await-of 的逃出(labeled break / + /// continue / return)需要异步 await 迭代器 return() 的 promise,走 + /// 异步挂起机制另行实现——此处只计同步 for-of,避免运行时同步调用异步 + /// 迭代器的 return()。 + pub(crate) fn is_for_of(self) -> bool { + matches!(self, LoopKind::ForOf) + } + + pub(crate) fn is_for_in(self) -> bool { + matches!(self, LoopKind::ForIn) + } +} + +/// 循环打开时的词法快照:break/continue/return 逃出时据此计算需关闭的迭代器层数。 +#[derive(Debug, Clone, Copy)] +pub(crate) struct LoopEntry { + pub(crate) break_label: LabelId, + pub(crate) continue_label: LabelId, + /// 循环打开时嵌套的 finally 域数(finally 逃出计数用)。 + pub(crate) finally_depth_at_open: usize, + /// 循环打开时已打开的 for-of/for-await-of 循环数。 + pub(crate) for_of_depth_at_open: usize, + /// 循环打开时已打开的 for-in 循环数。 + pub(crate) for_in_depth_at_open: usize, + pub(crate) kind: LoopKind, +} + /// 跳转目标 / 标签语句解析状态。 pub(crate) struct LabelCtx { /// label id → 指令下标。id 连续递增,Vec 索引即 id;写入前须扩容。 pub(crate) label_pos: Vec>, - /// 每个条目记录循环打开时嵌套的 finally 域数(break/continue 跨越 finally 计数用)。 - pub(crate) loop_stack: Vec<(LabelId, LabelId, usize)>, - /// 每个条目记录 switch 打开时嵌套的 finally 域数。 - pub(crate) switch_stack: Vec<(LabelId, usize)>, + /// 每个条目记录循环打开时的词法快照(finally/for-of/for-in 深度)。 + pub(crate) loop_stack: Vec, + /// 每个条目记录 switch 打开时的词法快照(finally/for-of/for-in 深度): + /// switch 内 break 的逃出计数以打开点为准,switch 之前已打开的迭代器 + /// 不属于本次逃出。 + pub(crate) switch_stack: Vec<(LabelId, usize, usize, usize)>, /// 活动标签语句作用域(解析 `break label` / `continue label`)。 pub(crate) label_scopes: Vec, /// 等待绑定到下一个循环 continue 目标的标签名。 pub(crate) pending_loop_labels: Vec, /// 当前打开(正在 emit)的 try/finally 域数。 pub(crate) finally_depth: usize, + /// 当前打开的 for-of/for-await-of 循环数(逃出计数基数)。 + pub(crate) for_of_depth: usize, + /// 当前打开的 for-in 循环数(逃出计数基数)。 + pub(crate) for_in_depth: usize, pub(crate) label_counter: u32, } diff --git a/crates/oxide_emit/src/module.rs b/crates/oxide_emit/src/module.rs index bceb38e..7d882e3 100644 --- a/crates/oxide_emit/src/module.rs +++ b/crates/oxide_emit/src/module.rs @@ -130,8 +130,7 @@ impl Emitter { /// 编译 ES module:顶层 body + 递归依赖。 /// `module_path` 为模块文件的规范路径(依赖解析基准 = 其父目录)。 pub fn emit_program_module( - &self, program: &oxide_parser::Program, module_path: &str, - loader: &mut dyn ModuleSourceLoader, + &self, program: &oxide_parser::Program, module_path: &str, loader: &mut dyn ModuleSourceLoader, ) -> Result { crate::emit_debug!("emit_program_module: {} stmts", program.body.len()); let mut ctx = CompileCtx::new(); @@ -145,8 +144,8 @@ impl Emitter { /// 模块体 emit(顶层与依赖模块共用入口)。 #[allow(clippy::too_many_arguments)] fn emit_module_into_ctx( - &self, program: &oxide_parser::Program, module_path: &str, - loader: &mut dyn ModuleSourceLoader, path_stack: &mut Vec, ctx: &mut CompileCtx, + &self, program: &oxide_parser::Program, module_path: &str, loader: &mut dyn ModuleSourceLoader, + path_stack: &mut Vec, ctx: &mut CompileCtx, ) -> Result<(), String> { let body = &program.body; @@ -249,16 +248,18 @@ impl Emitter { let imported = module_export_name_str(&s.imported); if is_self { if !own_export_names.contains(&imported) && !has_unnamed_star { - return Err(format!( - "requested module export is not exported: {imported}" - )); + return Err(format!("requested module export is not exported: {imported}")); } let undef_idx = ctx.add_constant(Constant::Undefined); let undef_reg = ctx.alloc_reg(); ctx.inst(Inst::load_const(Operand::Reg(undef_reg), undef_idx)); self.emit_bind_target( - s.local.name.as_str(), undef_reg, VariableDeclarationKind::Const, true, - false, ctx, + s.local.name.as_str(), + undef_reg, + VariableDeclarationKind::Const, + true, + false, + ctx, )?; if let Ok(reg) = ctx.lookup(s.local.name.as_str()) { ctx.module_self_aliases.insert(imported, reg); @@ -268,7 +269,11 @@ impl Emitter { let val_reg = self.emit_module_call(ctx, "__moduleLinkGet", &[dep_ns_reg, name_reg])?; self.emit_bind_target( - s.local.name.as_str(), val_reg, VariableDeclarationKind::Const, true, false, + s.local.name.as_str(), + val_reg, + VariableDeclarationKind::Const, + true, + false, ctx, )?; } @@ -282,8 +287,12 @@ impl Emitter { let undef_reg = ctx.alloc_reg(); ctx.inst(Inst::load_const(Operand::Reg(undef_reg), undef_idx)); self.emit_bind_target( - s.local.name.as_str(), undef_reg, VariableDeclarationKind::Const, true, - false, ctx, + s.local.name.as_str(), + undef_reg, + VariableDeclarationKind::Const, + true, + false, + ctx, )?; if let Ok(reg) = ctx.lookup(s.local.name.as_str()) { ctx.module_self_aliases.insert("default".to_string(), reg); @@ -293,7 +302,11 @@ impl Emitter { let val_reg = self.emit_module_call(ctx, "__moduleLinkGet", &[dep_ns_reg, name_reg])?; self.emit_bind_target( - s.local.name.as_str(), val_reg, VariableDeclarationKind::Const, true, false, + s.local.name.as_str(), + val_reg, + VariableDeclarationKind::Const, + true, + false, ctx, )?; } @@ -302,7 +315,11 @@ impl Emitter { // 命名空间绑定引用 ns 对象本身:自导入时同一对象, // body 执行后导出自然可见。 self.emit_bind_target( - s.local.name.as_str(), dep_ns_reg, VariableDeclarationKind::Const, true, false, + s.local.name.as_str(), + dep_ns_reg, + VariableDeclarationKind::Const, + true, + false, ctx, )?; } @@ -353,14 +370,13 @@ impl Emitter { // —— 收尾:封冻命名空间并返回(顶层 RETURN 亦终止 run)—— self.emit_module_call(ctx, "__moduleSeal", &[ns_reg])?; - ctx.inst(Inst::new(OpCode::RETURN, Operand::Reg(ns_reg), Operand::None, Operand::None)); + ctx.inst(Inst::ret(Operand::Reg(ns_reg), 0, 0)); Ok(()) } /// 编译 JS 依赖模块(递归)。 fn compile_js_dep( - &self, resolved: &ResolvedModule, loader: &mut dyn ModuleSourceLoader, - path_stack: &mut Vec, + &self, resolved: &ResolvedModule, loader: &mut dyn ModuleSourceLoader, path_stack: &mut Vec, ) -> Result { let alloc = oxide_parser::Allocator::default(); let program = oxide_parser::parse_module(&alloc, &resolved.source).map_err(|errs| { @@ -381,25 +397,19 @@ impl Emitter { let kind_reg = self.load_string_const(kind, &mut ctx); let content_reg = self.load_string_const(content, &mut ctx); let val_reg = self.emit_module_call(&mut ctx, "__moduleData", &[kind_reg, content_reg])?; - ctx.inst(Inst::new(OpCode::RETURN, Operand::Reg(val_reg), Operand::None, Operand::None)); + ctx.inst(Inst::ret(Operand::Reg(val_reg), 0, 0)); Ok(ctx.assemble_ir(ParamLayout { base: 0, count: 0 }, None)) } /// 内部辅助调用:CALL_NATIVE 到全局 native(__module*),返回结果寄存器。 - pub(crate) fn emit_module_call( - &self, ctx: &mut CompileCtx, name: &str, arg_regs: &[u32], - ) -> Result { + pub(crate) fn emit_module_call(&self, ctx: &mut CompileCtx, name: &str, arg_regs: &[u32]) -> Result { let callee_reg = ctx.lookup_or_builtin(name)?; let this_reg = ctx.alloc_reg(); let undef_idx = ctx.add_constant(Constant::Undefined); ctx.inst(Inst::load_const(Operand::Reg(this_reg), undef_idx)); // CALL_NATIVE 按 regs[first_arg + i] 连续读参数:把非连续 vreg 打包为连续块。 let mut packed = arg_regs.to_vec(); - let first_arg_reg = if packed.is_empty() { - this_reg - } else { - pack_arg_regs(&mut packed, ctx) - }; + let first_arg_reg = if packed.is_empty() { this_reg } else { pack_arg_regs(&mut packed, ctx) }; ctx.inst(Inst::call_native( Operand::Reg(callee_reg), Operand::Reg(this_reg), @@ -448,9 +458,7 @@ impl Emitter { pub(crate) fn emit_module_export_domain( &self, stmt: &Statement, ctx: &mut CompileCtx, ) -> Result, String> { - let ns_reg = ctx - .module_ns_reg - .ok_or_else(|| "export outside module context".to_string())?; + let ns_reg = ctx.module_ns_reg.ok_or_else(|| "export outside module context".to_string())?; match stmt { Statement::ExportNamedDeclaration(exp) => { if let Some(decl) = &exp.declaration { @@ -487,9 +495,10 @@ impl Emitter { _ => return Err("unsupported export declaration".into()), } } else { - let dep_ns_opt = exp.source.as_ref().map(|src| { - ctx.module_dep_ns_regs.get(src.value.as_str()).copied() - }); + let dep_ns_opt = exp + .source + .as_ref() + .map(|src| ctx.module_dep_ns_regs.get(src.value.as_str()).copied()); for spec in &exp.specifiers { let local_name = module_export_name_str(&spec.local); let exported_name = module_export_name_str(&spec.exported); @@ -516,7 +525,12 @@ impl Emitter { let reg = self.emit_function_expression(fd, ctx)?; if let Some(id) = &fd.id { self.emit_bind_target( - id.name.as_str(), reg, VariableDeclarationKind::Const, true, false, ctx, + id.name.as_str(), + reg, + VariableDeclarationKind::Const, + true, + false, + ctx, )?; } reg @@ -525,7 +539,12 @@ impl Emitter { let reg = self.emit_class_expression(cl, ctx)?; if let Some(id) = &cl.id { self.emit_bind_target( - id.name.as_str(), reg, VariableDeclarationKind::Const, true, false, ctx, + id.name.as_str(), + reg, + VariableDeclarationKind::Const, + true, + false, + ctx, )?; } reg diff --git a/crates/oxide_emit/src/stmt/basic/break_continue.rs b/crates/oxide_emit/src/stmt/basic/break_continue.rs index 2b690ac..ddd22dd 100644 --- a/crates/oxide_emit/src/stmt/basic/break_continue.rs +++ b/crates/oxide_emit/src/stmt/basic/break_continue.rs @@ -1,8 +1,9 @@ //! break/continue 语句 emit:解析标签与最近循环的跳转目标。 //! -//! 计算 break/continue 逃出的 finally 域数(目标打开时的 finally 深度与当前深度之差): -//! 逃出 ≥1 个 finally → 发 BREAK/CONTINUE(运行时穿越 finally 后再跳转); -//! 不逃出(目标在 try 域内,如 switch/循环内部的 break)→ 发普通 JMP。 +//! 计算 break/continue 逃出的 finally 域数(目标打开时的 finally 深度与当前深度之差) +//! 与逃出的迭代器层数(for-of/for-in 深度之差,目标循环自身的关闭由 CLOSE / +//! FOR_IN_CLEANUP 处理,不计入):逃出 ≥1 个 finally 域或 ≥1 层迭代器 → 发 +//! BREAK/CONTINUE(运行时穿越 finally、关闭逃出迭代器后再跳转);否则发普通 JMP。 use crate::{CompileCtx, Emitter}; use oxide_ir::inst::Inst; @@ -11,21 +12,35 @@ impl Emitter { pub(crate) fn emit_break_statement( &self, stmt: &oxide_parser::BreakStatement, ctx: &mut CompileCtx, ) -> Result, String> { - let (break_label, fd_at_open) = if let Some(label) = &stmt.label { + let (break_label, fd_at_open, fod_at_open, fid_at_open) = if let Some(label) = &stmt.label { let name = label.name.as_str(); let scope = ctx .find_label(name) .ok_or_else(|| format!("SyntaxError: Undefined label '{name}'"))?; - (scope.break_label, scope.finally_depth_at_open) - } else if let Some((sw_label, fd)) = ctx.current_switch() { - (*sw_label, *fd) + ( + scope.break_label, + scope.finally_depth_at_open, + scope.for_of_depth_at_open, + scope.for_in_depth_at_open, + ) + } else if let Some((sw_label, fd, fod, fid)) = ctx.current_switch() { + // switch 内 break:目标为 switch 出口。逃出计数以 switch 打开时的 + // 深度快照为基准——switch 之前已打开的循环不属于本次逃出,仅当 + // case 内新打开了迭代循环(并逃出)时才需关闭。 + (*sw_label, *fd, *fod, *fid) } else { - let (bl, _, fd) = ctx.current_loop().ok_or("break outside switch or loop".to_string())?; - (*bl, *fd) + let entry = ctx.current_loop().ok_or("break outside switch or loop".to_string())?; + ( + entry.break_label, + entry.finally_depth_at_open, + entry.for_of_depth_at_open, + entry.for_in_depth_at_open, + ) }; let crossed = ctx.labels.finally_depth.saturating_sub(fd_at_open) as u16; - if crossed > 0 { - ctx.inst(Inst::brk(break_label, crossed)); + let (for_of_count, for_in_count) = escape_iter_counts(ctx, fod_at_open, fid_at_open); + if crossed > 0 || for_of_count > 0 || for_in_count > 0 { + ctx.inst(Inst::brk(break_label, crossed, for_of_count, for_in_count)); } else { ctx.inst(Inst::jmp(break_label)); } @@ -35,7 +50,7 @@ impl Emitter { pub(crate) fn emit_continue_statement( &self, stmt: &oxide_parser::ContinueStatement, ctx: &mut CompileCtx, ) -> Result, String> { - let (continue_label, fd_at_open) = if let Some(label) = &stmt.label { + let (continue_label, fd_at_open, fod_at_open, fid_at_open) = if let Some(label) = &stmt.label { let name = label.name.as_str(); let scope = ctx .find_label(name) @@ -45,17 +60,37 @@ impl Emitter { format!("SyntaxError: Illegal continue statement: '{name}' does not denote an iteration statement") })?, scope.finally_depth_at_open, + scope.for_of_depth_at_open, + scope.for_in_depth_at_open, ) } else { - let (_, cl, fd) = ctx.current_loop().ok_or("continue outside loop".to_string())?; - (*cl, *fd) + let entry = ctx.current_loop().ok_or("continue outside loop".to_string())?; + ( + entry.continue_label, + entry.finally_depth_at_open, + entry.for_of_depth_at_open, + entry.for_in_depth_at_open, + ) }; let crossed = ctx.labels.finally_depth.saturating_sub(fd_at_open) as u16; - if crossed > 0 { - ctx.inst(Inst::cont(continue_label, crossed)); + let (for_of_count, for_in_count) = escape_iter_counts(ctx, fod_at_open, fid_at_open); + if crossed > 0 || for_of_count > 0 || for_in_count > 0 { + ctx.inst(Inst::cont(continue_label, crossed, for_of_count, for_in_count)); } else { ctx.inst(Inst::jmp(continue_label)); } Ok(None) } } + +/// 计算逃出目标循环需要关闭的 for-of / for-in 迭代器层数。 +/// +/// 深度快照(`fod_at_open`/`fid_at_open`)取目标循环打开后(含自身)的深度: +/// 当前深度与之差 = 被逃出的中间层数。目标循环自身的关闭由循环收尾指令 +/// (FOR_OF_CLOSE / FOR_IN_CLEANUP)处理,不计入——break 的跳转目标在收尾指令 +/// 之后(含其 CLOSE),continue 则目标循环继续迭代。 +fn escape_iter_counts(ctx: &CompileCtx, fod_at_open: usize, fid_at_open: usize) -> (usize, usize) { + let for_of_count = ctx.labels.for_of_depth.saturating_sub(fod_at_open); + let for_in_count = ctx.labels.for_in_depth.saturating_sub(fid_at_open); + (for_of_count, for_in_count) +} diff --git a/crates/oxide_emit/src/stmt/basic/return_stmt.rs b/crates/oxide_emit/src/stmt/basic/return_stmt.rs index a548c0b..e461901 100644 --- a/crates/oxide_emit/src/stmt/basic/return_stmt.rs +++ b/crates/oxide_emit/src/stmt/basic/return_stmt.rs @@ -11,6 +11,9 @@ impl Emitter { let Statement::ReturnStatement(ret) = stmt else { return Ok(None); }; + // return 逃出全部打开的迭代循环:for-of 需 IteratorClose,for-in 弹出迭代器。 + let for_of_count = ctx.labels.for_of_depth; + let for_in_count = ctx.labels.for_in_depth; match &ret.argument { Some(expr) => { let r = self.emit_expression(expr, ctx)?; @@ -18,12 +21,18 @@ impl Emitter { // 弹出连续纯 catch handler(finally handler 走运行时完成穿越逐个 // 执行)。否则 return 跳过 TRY_END 会让 handler 残留在 try_stack, // 后续异常 unwind 会跳回已返回函数的 catch 形成死循环。 - self.emit_return_try_cleanup(ctx); - ctx.inst(Inst::new(OpCode::RETURN, Operand::Reg(r), Operand::None, Operand::None)); + // 逃出迭代器时(close_count>0)保留 handler:return() 抛错须经 + // unwind 被外围 catch 捕获(新错误替代完成值),弹出后无法展开。 + if for_of_count == 0 && for_in_count == 0 { + self.emit_return_try_cleanup(ctx); + } + ctx.inst(Inst::ret(Operand::Reg(r), for_of_count, for_in_count)); } None => { - self.emit_return_try_cleanup(ctx); - ctx.inst(Inst::new(OpCode::RETURN, Operand::None, Operand::None, Operand::None)); + if for_of_count == 0 && for_in_count == 0 { + self.emit_return_try_cleanup(ctx); + } + ctx.inst(Inst::ret(Operand::None, for_of_count, for_in_count)); } } Ok(None) diff --git a/crates/oxide_emit/src/stmt/iteration/do_while.rs b/crates/oxide_emit/src/stmt/iteration/do_while.rs index 69a8c91..0fb5d24 100644 --- a/crates/oxide_emit/src/stmt/iteration/do_while.rs +++ b/crates/oxide_emit/src/stmt/iteration/do_while.rs @@ -14,7 +14,7 @@ impl Emitter { let start_label = ctx.next_label_id(); let end_label = ctx.next_label_id(); ctx.labels.set_label_pos(start_label, ctx.insts.len()); - ctx.push_loop(end_label, start_label); + ctx.push_loop(end_label, start_label, crate::emit_ctx::LoopKind::Plain); let n_labeled = ctx.take_pending_loop_labels(end_label, start_label); self.emit_statement(&dw.body, ctx)?; let test_reg = self.emit_expression(&dw.test, ctx)?; diff --git a/crates/oxide_emit/src/stmt/iteration/for_.rs b/crates/oxide_emit/src/stmt/iteration/for_.rs index 518d8ad..ddbc2e7 100644 --- a/crates/oxide_emit/src/stmt/iteration/for_.rs +++ b/crates/oxide_emit/src/stmt/iteration/for_.rs @@ -40,7 +40,7 @@ impl Emitter { let start_label = ctx.next_label_id(); let update_label = ctx.next_label_id(); let end_label = ctx.next_label_id(); - ctx.push_loop(end_label, update_label); + ctx.push_loop(end_label, update_label, crate::emit_ctx::LoopKind::Plain); let n_labeled = ctx.take_pending_loop_labels(end_label, update_label); // 循环头声明中被嵌套函数捕获的 let/const 绑定:每迭代 fresh cell。 let mut fresh_bindings: Vec<(String, u8)> = Vec::new(); diff --git a/crates/oxide_emit/src/stmt/iteration/for_in.rs b/crates/oxide_emit/src/stmt/iteration/for_in.rs index d692ed3..3b5f615 100644 --- a/crates/oxide_emit/src/stmt/iteration/for_in.rs +++ b/crates/oxide_emit/src/stmt/iteration/for_in.rs @@ -17,7 +17,7 @@ impl Emitter { let obj_reg = self.emit_expression(&fi.right, ctx)?; ctx.inst(Inst::new(OpCode::FOR_IN_INIT, Operand::None, Operand::Reg(obj_reg), Operand::None)); ctx.labels.set_label_pos(start_label, ctx.insts.len()); - ctx.push_loop(end_label, start_label); + ctx.push_loop(end_label, start_label, crate::emit_ctx::LoopKind::ForIn); let n_labeled = ctx.take_pending_loop_labels(end_label, start_label); let done_reg = ctx.alloc_reg(); ctx.inst(Inst::new(OpCode::FOR_IN_DONE, Operand::Reg(done_reg), Operand::None, Operand::None)); diff --git a/crates/oxide_emit/src/stmt/iteration/for_of.rs b/crates/oxide_emit/src/stmt/iteration/for_of.rs index 5ec366c..3ad5cb7 100644 --- a/crates/oxide_emit/src/stmt/iteration/for_of.rs +++ b/crates/oxide_emit/src/stmt/iteration/for_of.rs @@ -29,7 +29,7 @@ impl Emitter { let iter_src_reg = self.emit_expression(&fo.right, ctx)?; ctx.inst(Inst::new(OpCode::FOR_OF_INIT, Operand::None, Operand::Reg(iter_src_reg), Operand::None)); ctx.labels.set_label_pos(start_label, ctx.insts.len()); - ctx.push_loop(end_label, start_label); + ctx.push_loop(end_label, start_label, crate::emit_ctx::LoopKind::ForOf); let n_labeled = ctx.take_pending_loop_labels(end_label, start_label); let has_reg = ctx.alloc_reg(); ctx.inst(Inst::new(OpCode::FOR_OF_DONE, Operand::Reg(has_reg), Operand::None, Operand::None)); @@ -64,7 +64,7 @@ impl Emitter { Operand::None, )); ctx.labels.set_label_pos(start_label, ctx.insts.len()); - ctx.push_loop(end_label, start_label); + ctx.push_loop(end_label, start_label, crate::emit_ctx::LoopKind::ForAwaitOf); let n_labeled = ctx.take_pending_loop_labels(end_label, start_label); let next_reg = ctx.alloc_reg(); ctx.inst(Inst::new(OpCode::FOR_AWAIT_OF_NEXT, Operand::Reg(next_reg), Operand::None, Operand::None)); diff --git a/crates/oxide_emit/src/stmt/iteration/while_loop.rs b/crates/oxide_emit/src/stmt/iteration/while_loop.rs index 1da1880..8ccee23 100644 --- a/crates/oxide_emit/src/stmt/iteration/while_loop.rs +++ b/crates/oxide_emit/src/stmt/iteration/while_loop.rs @@ -12,7 +12,7 @@ impl Emitter { let start_label = ctx.next_label_id(); let end_label = ctx.next_label_id(); ctx.labels.set_label_pos(start_label, ctx.insts.len()); - ctx.push_loop(end_label, start_label); + ctx.push_loop(end_label, start_label, crate::emit_ctx::LoopKind::Plain); let n_labeled = ctx.take_pending_loop_labels(end_label, start_label); let test_reg = self.emit_expression(&wh.test, ctx)?; ctx.inst(Inst::jmp_if_false(test_reg, end_label)); diff --git a/crates/oxide_ir/src/inst.rs b/crates/oxide_ir/src/inst.rs index a2b8268..35cf836 100644 --- a/crates/oxide_ir/src/inst.rs +++ b/crates/oxide_ir/src/inst.rs @@ -368,14 +368,40 @@ impl Inst { /// break 完成:label 指向循环/switch 出口,`crossed` 为逃出的 finally 域数 /// (emit 词法计算,运行时据此逐个穿越 finally;0 表示未逃出,不产生完成语义)。 - /// 编码:rd 槽放 crossed(≤255),label 放 b 槽。 - pub fn brk(label: LabelId, crossed: u16) -> Self { - Self::new(OpCode::BREAK, Operand::Imm(crossed), Operand::None, Operand::Label(label)) + /// `for_of_count`/`for_in_count` 为逃出的迭代器层数,编码进 ext 字—— + /// BREAK/CONTINUE 的 a/b 槽被跳转 offset 占用,无空闲位。 + /// 编码:rd 槽放 crossed(≤255),label 放 b 槽,ext[0] 打包两个逃出计数。 + pub fn brk(label: LabelId, crossed: u16, for_of_count: usize, for_in_count: usize) -> Self { + Self::with_ext( + OpCode::BREAK, + Operand::Imm(crossed), + Operand::None, + Operand::Label(label), + &[pack_escape_counts(for_of_count, for_in_count)], + ) } /// continue 完成:同 break,label 指向循环继续目标。 - pub fn cont(label: LabelId, crossed: u16) -> Self { - Self::new(OpCode::CONTINUE, Operand::Imm(crossed), Operand::None, Operand::Label(label)) + pub fn cont(label: LabelId, crossed: u16, for_of_count: usize, for_in_count: usize) -> Self { + Self::with_ext( + OpCode::CONTINUE, + Operand::Imm(crossed), + Operand::None, + Operand::Label(label), + &[pack_escape_counts(for_of_count, for_in_count)], + ) + } + + /// return 完成:`src` 为返回值寄存器(None = 隐式 undefined,reg 0)。 + /// `for_of_count`/`for_in_count` 为逃出的迭代器层数,编码进 ext 字。 + pub fn ret(src: Operand, for_of_count: usize, for_in_count: usize) -> Self { + Self::with_ext( + OpCode::RETURN, + src, + Operand::None, + Operand::None, + &[pack_escape_counts(for_of_count, for_in_count)], + ) } /// 条件寄存器为 false 时跳转。 @@ -409,6 +435,12 @@ impl Inst { } } +/// 把逃出的 for-of / for-in 层数打包进一个 ext 字:低 16 位 for-of,高 16 位 for-in。 +/// 层数受寄存器上限约束(每层循环消耗若干 vreg),16 位覆盖远超实际可能。 +fn pack_escape_counts(for_of_count: usize, for_in_count: usize) -> u32 { + ((for_in_count.min(0xFFFF) as u32) << 16) | (for_of_count.min(0xFFFF) as u32) +} + /// spread 调用系 ext 构造:首字打包 `nstatic | (nspread << 8)`,后续按源码求值序排列 /// 每个实参字(静态实参 = 寄存器号,spread 源 = `0x8000_0000 | 寄存器号`)。 fn spread_ext(words: &[u32]) -> SmallVec<[u32; 4]> { diff --git a/crates/oxide_ir/src/lower.rs b/crates/oxide_ir/src/lower.rs index 16619e6..14eaac4 100644 --- a/crates/oxide_ir/src/lower.rs +++ b/crates/oxide_ir/src/lower.rs @@ -142,6 +142,9 @@ fn encode_inst(inst: &Inst, instrs: &mut Vec, jumps: &mut Vec<(usize, u32)> _ => unreachable!("is_jump_op mismatch"), }; instrs.push(instr); + // BREAK/CONTINUE 携带逃出迭代器层数的 ext 字;offset 回填只改写首条指令, + // ext 字随指令长度计入后续偏移(inst_to_instr 已含 ext)。 + instrs.extend_from_slice(&inst.ext); jumps.push((start, label)); return Ok(()); } diff --git a/crates/oxide_vm/src/generator.rs b/crates/oxide_vm/src/generator.rs index a5ae249..f5705a7 100644 --- a/crates/oxide_vm/src/generator.rs +++ b/crates/oxide_vm/src/generator.rs @@ -647,21 +647,33 @@ impl Vm { /// - `Ok(Some(value))`:直接完成,无需再 dispatch(`value` 为返回值); /// - `Ok(None)`:进入 finally 穿越,调用方须继续 `dispatch()`。 pub(crate) fn complete_generator_return(&mut self, value: JsValue) -> Result, String> { - self.pop_frame_catch_handlers(); + // 纯 catch handler 的清理延后到完成消费处:record_completion 在 finally + // 穿越路径弹出逃出的 catch-only handler;无 finally 时 do_return 弹帧前 + // 兜底清理。若提前弹出,return() 抛错经 unwind 展开时将找不到外围 catch。 let crossed = self .try_stack .iter() .filter(|h| h.frame_depth == self.frames.len() && h.finally_pc.is_some()) .count(); + // .return()/.throw() 注入时生成器挂起快照中打开的全部迭代器一并逃出 + // (栈上迭代器属于本生成器),完成恢复处统一关闭。 + let for_of_count = self.iters.for_of_iters.len(); + let for_in_count = self.iters.for_in_iters.len(); if let Some(finally_pc) = self.record_completion(crate::vm::Completion::Return { value, remaining_finally: crossed, + for_of_count, + for_in_count, }) { self.pc = finally_pc; return Ok(None); } - // 无 finally:直接交付返回(弹出生成器帧后 frames 为空 → Some(value))。 - self.do_return(value) + // 无 finally:关闭逃出迭代器后直接交付返回(弹出生成器帧后 frames 为空 → Some(value))。 + match self.close_escaped_iters(for_of_count, for_in_count) { + Ok(true) => self.do_return(value), + Ok(false) => Ok(None), + Err(e) => Err(e), + } } } diff --git a/crates/oxide_vm/src/session_gc.rs b/crates/oxide_vm/src/session_gc.rs index 7722148..45cdb6b 100644 --- a/crates/oxide_vm/src/session_gc.rs +++ b/crates/oxide_vm/src/session_gc.rs @@ -842,6 +842,7 @@ mod tests { vm.iters.for_of_iters.push(crate::vm_state::ForOfEntry { iterator: JsValue::from_js_object(child_session), last_result: JsValue::from_js_object(root_session), + is_async: false, }); let mut roots = Vec::new(); @@ -876,6 +877,8 @@ mod tests { vm.pending_completion = Some(crate::vm::Completion::Return { value: JsValue::from_js_object(session), remaining_finally: 0, + for_of_count: 0, + for_in_count: 0, }); let mut forwarding = std::collections::HashMap::new(); diff --git a/crates/oxide_vm/src/suspended.rs b/crates/oxide_vm/src/suspended.rs index cbbc02f..62564e3 100644 --- a/crates/oxide_vm/src/suspended.rs +++ b/crates/oxide_vm/src/suspended.rs @@ -44,7 +44,8 @@ pub(crate) struct SuspendedFrame { impl SuspendedFrame { /// 全空帧(New 阶段构造状态盒时用)。 - pub fn new_empty() -> Self { SuspendedFrame { + pub fn new_empty() -> Self { + SuspendedFrame { regs: Box::new([JsValue::undefined(); 256]), pc: 0, bytecode: Arc::default(), @@ -233,9 +234,16 @@ impl SuspendedFrame { self.exception_value = self.exception_value.map(&mut rewrite); self.pending_exception = self.pending_exception.map(&mut rewrite); self.pending_completion = self.pending_completion.map(|completion| match completion { - Completion::Return { value, remaining_finally } => Completion::Return { + Completion::Return { + value, + remaining_finally, + for_of_count, + for_in_count, + } => Completion::Return { value: rewrite(value), remaining_finally, + for_of_count, + for_in_count, }, other => other, }); @@ -295,6 +303,7 @@ impl SuspendedFrame { .map(|entry| ForOfEntry { iterator: rewrite(entry.iterator), last_result: rewrite(entry.last_result), + is_async: entry.is_async, }) .collect(), delegated_iterator: self.delegated_iterator.map(&mut rewrite), @@ -304,9 +313,16 @@ impl SuspendedFrame { pending_exception: self.pending_exception.map(&mut rewrite), pending_error_kind: self.pending_error_kind, pending_completion: self.pending_completion.map(|completion| match completion { - Completion::Return { value, remaining_finally } => Completion::Return { + Completion::Return { + value, + remaining_finally, + for_of_count, + for_in_count, + } => Completion::Return { value: rewrite(value), remaining_finally, + for_of_count, + for_in_count, }, other => other, }), @@ -375,16 +391,21 @@ mod tests { frame.for_of_iters.push(ForOfEntry { iterator: JsValue::float(6.0), last_result: JsValue::float(7.0), + is_async: false, }); frame.delegated_iterator = Some(JsValue::float(8.0)); frame.saved_bytecode_stack.push(Arc::from(vec![0u32])); - frame.saved_immutables_stack.push(std::ptr::slice_from_raw_parts(std::ptr::null(), 0)); + frame + .saved_immutables_stack + .push(std::ptr::slice_from_raw_parts(std::ptr::null(), 0)); frame.exception_value = Some(JsValue::float(9.0)); frame.pending_exception = Some(JsValue::float(10.0)); frame.pending_error_kind = Some("Error"); frame.pending_completion = Some(Completion::Return { value: JsValue::float(11.0), remaining_finally: 0, + for_of_count: 0, + for_in_count: 0, }); } @@ -426,6 +447,8 @@ mod tests { machine.pending_completion = Some(Completion::Return { value: JsValue::float(46.0), remaining_finally: 1, + for_of_count: 0, + for_in_count: 0, }); let mut frame = SuspendedFrame::new_empty(); @@ -435,7 +458,7 @@ mod tests { assert_eq!(frame.delegated_iterator, Some(JsValue::float(45.0))); assert!(matches!( frame.pending_completion, - Some(Completion::Return { value, remaining_finally: 1 }) if value == JsValue::float(46.0) + Some(Completion::Return { value, remaining_finally: 1, .. }) if value == JsValue::float(46.0) )); // sub_modules 为空表:恢复应报"跨 run"错(保持现有三处行为)。 diff --git a/crates/oxide_vm/src/vm.rs b/crates/oxide_vm/src/vm.rs index 087b9c2..3433818 100644 --- a/crates/oxide_vm/src/vm.rs +++ b/crates/oxide_vm/src/vm.rs @@ -251,11 +251,28 @@ pub struct TryHandler { /// /// 仿 `pending_exception` 的侧通道:finally 执行期间悬挂在此,由 TRY_FINALLY_END /// 逐个恢复(`remaining_finally` 为仍需穿越的 finally 体数,进入一个递减一个)。 +/// `for_of_count`/`for_in_count` 为逃出时需关闭的迭代器层数:在全部 finally 穿越 +/// 之后、跳转/返回之前执行(规范 §13.7.5.4 的 IteratorClose 在完成值之后)。 #[derive(Debug, Clone, Copy)] pub enum Completion { - Break { target_pc: usize, remaining_finally: usize }, - Continue { target_pc: usize, remaining_finally: usize }, - Return { value: JsValue, remaining_finally: usize }, + Break { + target_pc: usize, + remaining_finally: usize, + for_of_count: usize, + for_in_count: usize, + }, + Continue { + target_pc: usize, + remaining_finally: usize, + for_of_count: usize, + for_in_count: usize, + }, + Return { + value: JsValue, + remaining_finally: usize, + for_of_count: usize, + for_in_count: usize, + }, } impl Completion { @@ -268,20 +285,59 @@ impl Completion { } } + /// 逃出时需关闭的 for-of 迭代器层数。 + pub fn for_of_count(&self) -> usize { + match *self { + Completion::Break { for_of_count, .. } + | Completion::Continue { for_of_count, .. } + | Completion::Return { for_of_count, .. } => for_of_count, + } + } + + /// 逃出时需弹出的 for-in 迭代器层数。 + pub fn for_in_count(&self) -> usize { + match *self { + Completion::Break { for_in_count, .. } + | Completion::Continue { for_in_count, .. } + | Completion::Return { for_in_count, .. } => for_in_count, + } + } + /// 复制并改写剩余 finally 计数(进入一个 finally 后递减)。 pub fn with_remaining(&self, remaining: usize) -> Completion { match *self { - Completion::Break { target_pc, .. } => Completion::Break { + Completion::Break { + target_pc, + for_of_count, + for_in_count, + .. + } => Completion::Break { target_pc, remaining_finally: remaining, + for_of_count, + for_in_count, }, - Completion::Continue { target_pc, .. } => Completion::Continue { + Completion::Continue { + target_pc, + for_of_count, + for_in_count, + .. + } => Completion::Continue { target_pc, remaining_finally: remaining, + for_of_count, + for_in_count, }, - Completion::Return { value, .. } => Completion::Return { + Completion::Return { + value, + for_of_count, + for_in_count, + .. + } => Completion::Return { value, remaining_finally: remaining, + for_of_count, + for_in_count, }, } } @@ -779,9 +835,16 @@ impl Vm { self.pending_exception = self.pending_exception.map(&mut rewrite); self.last_uncaught_value = self.last_uncaught_value.map(&mut rewrite); self.pending_completion = self.pending_completion.map(|completion| match completion { - Completion::Return { value, remaining_finally } => Completion::Return { + Completion::Return { + value, + remaining_finally, + for_of_count, + for_in_count, + } => Completion::Return { value: rewrite(value), remaining_finally, + for_of_count, + for_in_count, }, other => other, }); @@ -1569,11 +1632,11 @@ impl Vm { } OpCode::BREAK => { - self.dispatch_break(instr); + self.dispatch_break(instr)?; } OpCode::CONTINUE => { - self.dispatch_continue(instr); + self.dispatch_continue(instr)?; } OpCode::JMP_IF_FALSE => { @@ -1696,7 +1759,7 @@ impl Vm { self.dispatch_define_accessor_attrs(rd, a, b, key_word, attrs)?; } - OpCode::RETURN => match self.dispatch_return(rd) { + OpCode::RETURN => match self.dispatch_return(instr) { Ok(Some(result)) => return Ok(result), Ok(None) => {} Err(e) => return Err(e), @@ -2383,6 +2446,7 @@ mod tests { vm.iters.for_of_iters.push(ForOfEntry { iterator: JsValue::undefined(), last_result: JsValue::undefined(), + is_async: false, }); vm.saved_bytecode_stack .push(Arc::from(vec![opcode::encode(opcode::OpCode::HALT, 0, 0, 0)])); @@ -2442,6 +2506,7 @@ mod tests { vm.iters.for_of_iters.push(ForOfEntry { iterator: JsValue::undefined(), last_result: JsValue::undefined(), + is_async: false, }); vm.run(&module).expect("FOR_OF_CLOSE should tolerate non-object sentinel"); diff --git a/crates/oxide_vm/src/vm_dispatch_ctrl.rs b/crates/oxide_vm/src/vm_dispatch_ctrl.rs index ff867a6..7bdc472 100644 --- a/crates/oxide_vm/src/vm_dispatch_ctrl.rs +++ b/crates/oxide_vm/src/vm_dispatch_ctrl.rs @@ -286,33 +286,63 @@ impl Vm { /// break 完成:`crossed`(rd 槽)为 emit 词法算出的逃出 finally 域数。 /// 逐个穿越 finally 后跳转到目标;crossed 为 0 时直接跳转。 - pub(crate) fn dispatch_break(&mut self, instr: u32) { + /// ext 字携带逃出的迭代器层数:无 finally 穿越时立即关闭后跳转,有 finally + /// 时计数随 Completion 悬挂,由 TRY_FINALLY_END 在穿越完成后消费。 + pub(crate) fn dispatch_break(&mut self, instr: u32) -> Result<(), String> { + let (for_of_count, for_in_count) = self.read_escape_counts(); let offset = opcode::offset16(instr) as isize; let target_pc = ((self.pc as isize) + offset - 1) as usize; let crossed = opcode::rd(instr) as usize; if let Some(finally_pc) = self.record_completion(Completion::Break { target_pc, remaining_finally: crossed, + for_of_count, + for_in_count, }) { self.pc = finally_pc; } else { - self.pc = target_pc; + // 无 finally 穿越:关闭逃出迭代器后跳转;return() 抛错被接管时 + // unwind 已设 pc,跳过跳转。 + match self.close_escaped_iters(for_of_count, for_in_count) { + Ok(true) => self.pc = target_pc, + Ok(false) => {} + Err(e) => return Err(e), + } } + Ok(()) } /// continue 完成:同 break,目标为循环继续位置。 - pub(crate) fn dispatch_continue(&mut self, instr: u32) { + pub(crate) fn dispatch_continue(&mut self, instr: u32) -> Result<(), String> { + let (for_of_count, for_in_count) = self.read_escape_counts(); let offset = opcode::offset16(instr) as isize; let target_pc = ((self.pc as isize) + offset - 1) as usize; let crossed = opcode::rd(instr) as usize; if let Some(finally_pc) = self.record_completion(Completion::Continue { target_pc, remaining_finally: crossed, + for_of_count, + for_in_count, }) { self.pc = finally_pc; } else { - self.pc = target_pc; + // 无 finally 穿越:关闭逃出迭代器后跳转;return() 抛错被接管时 + // unwind 已设 pc,跳过跳转。 + match self.close_escaped_iters(for_of_count, for_in_count) { + Ok(true) => self.pc = target_pc, + Ok(false) => {} + Err(e) => return Err(e), + } } + Ok(()) + } + + /// 读取 BREAK/CONTINUE/RETURN 的 ext 字逃出计数(低 16 位 for-of,高 16 位 + /// for-in)。调用时机:主循环已把 pc 推进到 ext 字位置;两条跳转/返回路径 + /// 都会覆盖 pc,此处无需再推进。 + fn read_escape_counts(&self) -> (usize, usize) { + let packed = self.bytecode[self.pc]; + ((packed & 0xFFFF) as usize, (packed >> 16) as usize) } /// 记录一次控制流完成:穿越 `remaining_finally` 个 finally 体后执行完成本身。 @@ -365,19 +395,20 @@ impl Vm { /// return 完成:`crossed` = 当前帧深度内全部 finally handler 数(return 逃出整个 /// 函数,途中被覆盖的 finally 体也在内),穿越后实际返回。 - pub(crate) fn dispatch_return(&mut self, rd: usize) -> Result, String> { + /// ext 字携带逃出的迭代器层数,由 TRY_FINALLY_END 在穿越完成后关闭。 + pub(crate) fn dispatch_return(&mut self, instr: u32) -> Result, String> { + let rd = opcode::rd(instr) as usize; let result = self.regs[rd]; + let (for_of_count, for_in_count) = self.read_escape_counts(); crate::vm_debug!( "RETURN depth={} saved_pc={}", self.frames.len(), self.frames.last().map(|f| f.return_addr).unwrap_or(0) ); - // return 逃出整个函数:当前帧残留的纯 catch handler 一律弹出(return 不被 - // catch 捕获),finally handler 保留给下方完成穿越逐个执行。即使 emit 侧已 - // 从栈顶弹出连续 catch,这里仍扫描兜底——finally 之下的 catch 无法由 - // TRY_END 直接弹出。防 handler 泄漏到已返回函数,导致后续异常 unwind - // 跳回死函数的 catch 形成死循环。 - self.pop_frame_catch_handlers(); + // 纯 catch handler 的清理延后到完成消费处:record_completion 在 finally + // 穿越路径弹出逃出的 catch-only handler;无 finally 时 do_return 弹帧前 + // 兜底清理。若在此提前弹出,return() 抛错经 unwind 展开时将找不到 + // 外围 catch(规范要求新错误替代完成值继续被捕获)。 let crossed = self .try_stack .iter() @@ -386,11 +417,19 @@ impl Vm { if let Some(finally_pc) = self.record_completion(Completion::Return { value: result, remaining_finally: crossed, + for_of_count, + for_in_count, }) { self.pc = finally_pc; return Ok(None); } - self.do_return(result) + // 无 finally 穿越:关闭逃出迭代器后返回;return() 抛错被接管时原完成值 + // 被新错误替代,跳过实际返回。 + match self.close_escaped_iters(for_of_count, for_in_count) { + Ok(true) => self.do_return(result), + Ok(false) => Ok(None), + Err(e) => Err(e), + } } /// 弹出当前帧内所有残留的纯 catch handler(无 finally 域),供 return 逃出 @@ -562,14 +601,79 @@ impl Vm { } } match c { - Completion::Break { target_pc, .. } | Completion::Continue { target_pc, .. } => { - self.pc = target_pc; + Completion::Break { + target_pc, + for_of_count, + for_in_count, + .. + } + | Completion::Continue { + target_pc, + for_of_count, + for_in_count, + .. + } => { + // 全部 finally 穿越完成、真正跳转前关闭逃出迭代器——与规范顺序 + // (循环体完成值已含内部 try-finally 处理,IteratorClose 在后)一致。 + // return() 抛错被接管时 unwind 已设 pc,跳过跳转。 + match self.close_escaped_iters(for_of_count, for_in_count) { + Ok(true) => self.pc = target_pc, + Ok(false) => {} + Err(e) => return Err(e), + } Ok(None) } - Completion::Return { value, .. } => self.do_return(value), + Completion::Return { + value, + for_of_count, + for_in_count, + .. + } => match self.close_escaped_iters(for_of_count, for_in_count) { + Ok(true) => self.do_return(value), + Ok(false) => Ok(None), + Err(e) => Err(e), + }, } } + /// 逃出关闭:按 LIFO 弹出逃出的 for-in 迭代器(无 return() 语义),再关闭逃出的 + /// for-of 迭代器。异步迭代器条目(for-await-of)跳过——其 return() 返回 promise + /// 须 await 后结算,走独立异步关闭机制。逃出路径非 suppress:return() 抛错经 + /// raise_call_error 展开,剩余迭代器由 unwind 的 close_for_of_above 以 suppress + /// 继续关闭——新错误替代原完成值向外传播。 + /// + /// # 返回值 + /// `Ok(true)` = 全部关闭完成,调用方继续跳转/返回动作;`Ok(false)` = return() + /// 抛错且异常已被外围 catch/finally 接管(unwind 已改写 pc),调用方须丢弃 + /// 原完成动作;`Err` = 无处理器,异常逃逸。 + pub(crate) fn close_escaped_iters(&mut self, for_of_count: usize, for_in_count: usize) -> Result { + for _ in 0..for_in_count { + self.iters.pop_for_in(); + } + for _ in 0..for_of_count { + let Some(entry) = self.iters.pop_for_of() else { + break; + }; + // 异步迭代器不在此关闭:for-await-of 的逃出(labeled/return)由 + // 异步关闭机制处理,此处同步关闭会跳过 return() promise 的等待。 + if entry.is_async { + continue; + } + let pc_before = self.pc; + match self.close_for_of_iterator(entry.iterator, false) { + Ok(()) => {} + Err(e) => return Err(e), + } + // return() 抛错被接管:unwind 把 pc 改写为 catch/finally 入口,或挂起 + // 在途异常等 finally 穿越。正常关闭时 pc 不变(call_function_sync 保存 + // 并恢复调用方 pc)。 + if self.pc != pc_before || self.pending_exception.is_some() { + return Ok(false); + } + } + Ok(true) + } + /// 找下一个未被覆盖的包裹 finally 并进入(resume 路径用):弹出途中被逃出的 /// catch-only 与防御性的 active handler。无更多返回 None。 fn find_next_finally(&mut self) -> Option { diff --git a/crates/oxide_vm/src/vm_dispatch_misc.rs b/crates/oxide_vm/src/vm_dispatch_misc.rs index 76d8075..a1ef7ee 100644 --- a/crates/oxide_vm/src/vm_dispatch_misc.rs +++ b/crates/oxide_vm/src/vm_dispatch_misc.rs @@ -537,7 +537,7 @@ impl Vm { let iterable = self.regs[a]; match oxide_builtins::iterator::make_iterator_for_value(self, iterable) { Ok(iterator) => { - self.iters.push_for_of(iterator); + self.iters.push_for_of(iterator, false); Ok(()) } Err(err) => { @@ -555,7 +555,7 @@ impl Vm { let iterable = self.regs[a]; match crate::async_from_sync::make_async_iterator(self, iterable) { Ok(iterator) => { - self.iters.push_for_of(iterator); + self.iters.push_for_of(iterator, true); Ok(()) } Err(err) => { diff --git a/crates/oxide_vm/src/vm_runtime.rs b/crates/oxide_vm/src/vm_runtime.rs index 6ba4f7e..eff8ef5 100644 --- a/crates/oxide_vm/src/vm_runtime.rs +++ b/crates/oxide_vm/src/vm_runtime.rs @@ -34,31 +34,81 @@ fn place_flat(module: &Arc, out: &mut Vec { std::mem::take(&mut $window_regs).into_boxed_slice() }; - ($recv:ident, $window_regs:ident, saved_this, copy) => { $recv.regs[254] }; - ($recv:ident, $window_regs:ident, saved_new_target, copy) => { $recv.regs[255] }; - ($recv:ident, $window_regs:ident, pc, copy) => { $recv.pc }; - ($recv:ident, $window_regs:ident, bytecode, move_field) => { std::mem::take(&mut $recv.bytecode) }; - ($recv:ident, $window_regs:ident, active_immutables, copy) => { $recv.active_immutables }; - ($recv:ident, $window_regs:ident, active_reg_limit, copy) => { $recv.active_reg_limit }; - ($recv:ident, $window_regs:ident, root_reg_limit, copy) => { $recv.root_reg_limit }; - ($recv:ident, $window_regs:ident, try_stack, move_field) => { std::mem::take(&mut $recv.try_stack) }; - ($recv:ident, $window_regs:ident, frames, frames_values) => { std::mem::take(&mut $recv.frames) }; - ($recv:ident, $window_regs:ident, exception_value, opt_take) => { $recv.exception_value.take() }; - ($recv:ident, $window_regs:ident, pending_exception, opt_take) => { $recv.pending_exception.take() }; - ($recv:ident, $window_regs:ident, pending_error_kind, opt_take) => { $recv.pending_error_kind.take() }; - ($recv:ident, $window_regs:ident, pending_completion, opt_copy) => { $recv.pending_completion }; - ($recv:ident, $window_regs:ident, for_in_iters, for_in_keys) => { std::mem::take(&mut $recv.iters.for_in_iters) }; - ($recv:ident, $window_regs:ident, for_of_iters, iter_take) => { std::mem::take(&mut $recv.iters.for_of_iters) }; - ($recv:ident, $window_regs:ident, saved_bytecode_stack, move_field) => { std::mem::take(&mut $recv.saved_bytecode_stack) }; - ($recv:ident, $window_regs:ident, saved_immutables_stack, move_field) => { std::mem::take(&mut $recv.saved_immutables_stack) }; - ($recv:ident, $window_regs:ident, save_stack, move_field) => { std::mem::take(&mut $recv.save_stack) }; - ($recv:ident, $window_regs:ident, spill_stack, move_field) => { std::mem::take(&mut $recv.spill_stack) }; - ($recv:ident, $window_regs:ident, cell_stack, move_field) => { std::mem::take(&mut $recv.cell_stack) }; - ($recv:ident, $window_regs:ident, inline_callee, opt_copy) => { $recv.inline_callee }; - ($recv:ident, $window_regs:ident, inline_args_base, copy) => { $recv.inline_args_base }; - ($recv:ident, $window_regs:ident, inline_args_count, copy) => { $recv.inline_args_count }; - ($recv:ident, $window_regs:ident, accessor_frame_target_reg, copy) => { $recv.accessor_frame_target_reg }; + ($recv:ident, $window_regs:ident, regs, boxed_window) => { + std::mem::take(&mut $window_regs).into_boxed_slice() + }; + ($recv:ident, $window_regs:ident, saved_this, copy) => { + $recv.regs[254] + }; + ($recv:ident, $window_regs:ident, saved_new_target, copy) => { + $recv.regs[255] + }; + ($recv:ident, $window_regs:ident, pc, copy) => { + $recv.pc + }; + ($recv:ident, $window_regs:ident, bytecode, move_field) => { + std::mem::take(&mut $recv.bytecode) + }; + ($recv:ident, $window_regs:ident, active_immutables, copy) => { + $recv.active_immutables + }; + ($recv:ident, $window_regs:ident, active_reg_limit, copy) => { + $recv.active_reg_limit + }; + ($recv:ident, $window_regs:ident, root_reg_limit, copy) => { + $recv.root_reg_limit + }; + ($recv:ident, $window_regs:ident, try_stack, move_field) => { + std::mem::take(&mut $recv.try_stack) + }; + ($recv:ident, $window_regs:ident, frames, frames_values) => { + std::mem::take(&mut $recv.frames) + }; + ($recv:ident, $window_regs:ident, exception_value, opt_take) => { + $recv.exception_value.take() + }; + ($recv:ident, $window_regs:ident, pending_exception, opt_take) => { + $recv.pending_exception.take() + }; + ($recv:ident, $window_regs:ident, pending_error_kind, opt_take) => { + $recv.pending_error_kind.take() + }; + ($recv:ident, $window_regs:ident, pending_completion, opt_copy) => { + $recv.pending_completion + }; + ($recv:ident, $window_regs:ident, for_in_iters, for_in_keys) => { + std::mem::take(&mut $recv.iters.for_in_iters) + }; + ($recv:ident, $window_regs:ident, for_of_iters, iter_take) => { + std::mem::take(&mut $recv.iters.for_of_iters) + }; + ($recv:ident, $window_regs:ident, saved_bytecode_stack, move_field) => { + std::mem::take(&mut $recv.saved_bytecode_stack) + }; + ($recv:ident, $window_regs:ident, saved_immutables_stack, move_field) => { + std::mem::take(&mut $recv.saved_immutables_stack) + }; + ($recv:ident, $window_regs:ident, save_stack, move_field) => { + std::mem::take(&mut $recv.save_stack) + }; + ($recv:ident, $window_regs:ident, spill_stack, move_field) => { + std::mem::take(&mut $recv.spill_stack) + }; + ($recv:ident, $window_regs:ident, cell_stack, move_field) => { + std::mem::take(&mut $recv.cell_stack) + }; + ($recv:ident, $window_regs:ident, inline_callee, opt_copy) => { + $recv.inline_callee + }; + ($recv:ident, $window_regs:ident, inline_args_base, copy) => { + $recv.inline_args_base + }; + ($recv:ident, $window_regs:ident, inline_args_count, copy) => { + $recv.inline_args_count + }; + ($recv:ident, $window_regs:ident, accessor_frame_target_reg, copy) => { + $recv.accessor_frame_target_reg + }; } /// restore 方向的字段写回语句。`regs` 只回拷窗口并把缓冲归还池。 @@ -67,30 +117,78 @@ macro_rules! inline_restore_field { $recv.regs[..$saved.regs.len()].copy_from_slice(&$saved.regs); $recv.inline_reg_pool = Some($saved.regs.into_vec()); }; - ($recv:ident, $saved:ident, saved_this, copy) => { $recv.regs[254] = $saved.saved_this }; - ($recv:ident, $saved:ident, saved_new_target, copy) => { $recv.regs[255] = $saved.saved_new_target }; - ($recv:ident, $saved:ident, pc, copy) => { $recv.pc = $saved.pc }; - ($recv:ident, $saved:ident, bytecode, move_field) => { $recv.bytecode = $saved.bytecode }; - ($recv:ident, $saved:ident, active_immutables, copy) => { $recv.active_immutables = $saved.active_immutables }; - ($recv:ident, $saved:ident, active_reg_limit, copy) => { $recv.active_reg_limit = $saved.active_reg_limit }; - ($recv:ident, $saved:ident, root_reg_limit, copy) => { $recv.root_reg_limit = $saved.root_reg_limit }; - ($recv:ident, $saved:ident, try_stack, move_field) => { $recv.try_stack = $saved.try_stack }; - ($recv:ident, $saved:ident, frames, frames_values) => { $recv.frames = $saved.frames }; - ($recv:ident, $saved:ident, exception_value, opt_take) => { $recv.exception_value = $saved.exception_value }; - ($recv:ident, $saved:ident, pending_exception, opt_take) => { $recv.pending_exception = $saved.pending_exception }; - ($recv:ident, $saved:ident, pending_error_kind, opt_take) => { $recv.pending_error_kind = $saved.pending_error_kind }; - ($recv:ident, $saved:ident, pending_completion, opt_copy) => { $recv.pending_completion = $saved.pending_completion }; - ($recv:ident, $saved:ident, for_in_iters, for_in_keys) => { $recv.iters.for_in_iters = $saved.for_in_iters }; - ($recv:ident, $saved:ident, for_of_iters, iter_take) => { $recv.iters.for_of_iters = $saved.for_of_iters }; - ($recv:ident, $saved:ident, saved_bytecode_stack, move_field) => { $recv.saved_bytecode_stack = $saved.saved_bytecode_stack }; - ($recv:ident, $saved:ident, saved_immutables_stack, move_field) => { $recv.saved_immutables_stack = $saved.saved_immutables_stack }; - ($recv:ident, $saved:ident, save_stack, move_field) => { $recv.save_stack = $saved.save_stack }; - ($recv:ident, $saved:ident, spill_stack, move_field) => { $recv.spill_stack = $saved.spill_stack }; - ($recv:ident, $saved:ident, cell_stack, move_field) => { $recv.cell_stack = $saved.cell_stack }; - ($recv:ident, $saved:ident, inline_callee, opt_copy) => { $recv.inline_callee = $saved.inline_callee }; - ($recv:ident, $saved:ident, inline_args_base, copy) => { $recv.inline_args_base = $saved.inline_args_base }; - ($recv:ident, $saved:ident, inline_args_count, copy) => { $recv.inline_args_count = $saved.inline_args_count }; - ($recv:ident, $saved:ident, accessor_frame_target_reg, copy) => { $recv.accessor_frame_target_reg = $saved.accessor_frame_target_reg }; + ($recv:ident, $saved:ident, saved_this, copy) => { + $recv.regs[254] = $saved.saved_this + }; + ($recv:ident, $saved:ident, saved_new_target, copy) => { + $recv.regs[255] = $saved.saved_new_target + }; + ($recv:ident, $saved:ident, pc, copy) => { + $recv.pc = $saved.pc + }; + ($recv:ident, $saved:ident, bytecode, move_field) => { + $recv.bytecode = $saved.bytecode + }; + ($recv:ident, $saved:ident, active_immutables, copy) => { + $recv.active_immutables = $saved.active_immutables + }; + ($recv:ident, $saved:ident, active_reg_limit, copy) => { + $recv.active_reg_limit = $saved.active_reg_limit + }; + ($recv:ident, $saved:ident, root_reg_limit, copy) => { + $recv.root_reg_limit = $saved.root_reg_limit + }; + ($recv:ident, $saved:ident, try_stack, move_field) => { + $recv.try_stack = $saved.try_stack + }; + ($recv:ident, $saved:ident, frames, frames_values) => { + $recv.frames = $saved.frames + }; + ($recv:ident, $saved:ident, exception_value, opt_take) => { + $recv.exception_value = $saved.exception_value + }; + ($recv:ident, $saved:ident, pending_exception, opt_take) => { + $recv.pending_exception = $saved.pending_exception + }; + ($recv:ident, $saved:ident, pending_error_kind, opt_take) => { + $recv.pending_error_kind = $saved.pending_error_kind + }; + ($recv:ident, $saved:ident, pending_completion, opt_copy) => { + $recv.pending_completion = $saved.pending_completion + }; + ($recv:ident, $saved:ident, for_in_iters, for_in_keys) => { + $recv.iters.for_in_iters = $saved.for_in_iters + }; + ($recv:ident, $saved:ident, for_of_iters, iter_take) => { + $recv.iters.for_of_iters = $saved.for_of_iters + }; + ($recv:ident, $saved:ident, saved_bytecode_stack, move_field) => { + $recv.saved_bytecode_stack = $saved.saved_bytecode_stack + }; + ($recv:ident, $saved:ident, saved_immutables_stack, move_field) => { + $recv.saved_immutables_stack = $saved.saved_immutables_stack + }; + ($recv:ident, $saved:ident, save_stack, move_field) => { + $recv.save_stack = $saved.save_stack + }; + ($recv:ident, $saved:ident, spill_stack, move_field) => { + $recv.spill_stack = $saved.spill_stack + }; + ($recv:ident, $saved:ident, cell_stack, move_field) => { + $recv.cell_stack = $saved.cell_stack + }; + ($recv:ident, $saved:ident, inline_callee, opt_copy) => { + $recv.inline_callee = $saved.inline_callee + }; + ($recv:ident, $saved:ident, inline_args_base, copy) => { + $recv.inline_args_base = $saved.inline_args_base + }; + ($recv:ident, $saved:ident, inline_args_count, copy) => { + $recv.inline_args_count = $saved.inline_args_count + }; + ($recv:ident, $saved:ident, accessor_frame_target_reg, copy) => { + $recv.accessor_frame_target_reg = $saved.accessor_frame_target_reg + }; } /// 内联同步调用可搬移执行核心字段的单一登记表。save/restore 双向由本宏展开; @@ -98,32 +196,35 @@ macro_rules! inline_restore_field { /// `$window_regs` 只在 save 方向被 `regs` 字段消费。 macro_rules! inline_core_fields { ($recv:ident, $saved:ident, $ops:ident, $window_regs:ident) => { - $ops!($recv, $saved, $window_regs, - (regs, boxed_window), // V M - (saved_this, copy), // V - (saved_new_target, copy), // V - (pc, copy), // M - (bytecode, move_field), // V M - (active_immutables, copy), // M - (active_reg_limit, copy), // M - (root_reg_limit, copy), // M - (try_stack, move_field), // M - (frames, frames_values), // V M - (exception_value, opt_take), // V M - (pending_exception, opt_take), // V M - (pending_error_kind, opt_take), // M - (pending_completion, opt_copy), // V M - (for_in_iters, for_in_keys), // V M - (for_of_iters, iter_take), // V M - (saved_bytecode_stack, move_field), // M + $ops!( + $recv, + $saved, + $window_regs, + (regs, boxed_window), // V M + (saved_this, copy), // V + (saved_new_target, copy), // V + (pc, copy), // M + (bytecode, move_field), // V M + (active_immutables, copy), // M + (active_reg_limit, copy), // M + (root_reg_limit, copy), // M + (try_stack, move_field), // M + (frames, frames_values), // V M + (exception_value, opt_take), // V M + (pending_exception, opt_take), // V M + (pending_error_kind, opt_take), // M + (pending_completion, opt_copy), // V M + (for_in_iters, for_in_keys), // V M + (for_of_iters, iter_take), // V M + (saved_bytecode_stack, move_field), // M (saved_immutables_stack, move_field), // M - (save_stack, move_field), // V M - (spill_stack, move_field), // V M - (cell_stack, move_field), // V M - (inline_callee, opt_copy), // V M - (inline_args_base, copy), // M - (inline_args_count, copy), // M - (accessor_frame_target_reg, copy), // M + (save_stack, move_field), // V M + (spill_stack, move_field), // V M + (cell_stack, move_field), // V M + (inline_callee, opt_copy), // V M + (inline_args_base, copy), // M + (inline_args_count, copy), // M + (accessor_frame_target_reg, copy), // M ) }; } @@ -475,10 +576,13 @@ mod tests { vm.pending_completion = Some(Completion::Return { value: JsValue::float(8.0), remaining_finally: 1, + for_of_count: 0, + for_in_count: 0, }); vm.iters.for_of_iters.push(crate::vm_state::ForOfEntry { iterator: JsValue::float(8.5), last_result: JsValue::float(9.0), + is_async: false, }); vm.spill_stack.push(JsValue::float(10.0)); vm.save_stack.push(JsValue::float(11.0)); @@ -503,7 +607,7 @@ mod tests { assert_eq!(vm.pending_error_kind, Some("TypeError")); assert!(matches!( vm.pending_completion, - Some(Completion::Return { value, remaining_finally: 1 }) if value == JsValue::float(8.0) + Some(Completion::Return { value, remaining_finally: 1, .. }) if value == JsValue::float(8.0) )); assert_eq!(vm.iters.for_of_iters.len(), 1); assert_eq!(vm.iters.for_of_iters[0].iterator, JsValue::float(8.5)); diff --git a/crates/oxide_vm/src/vm_state.rs b/crates/oxide_vm/src/vm_state.rs index c13db18..d36c08d 100644 --- a/crates/oxide_vm/src/vm_state.rs +++ b/crates/oxide_vm/src/vm_state.rs @@ -127,6 +127,10 @@ pub(crate) struct ForOfEntry { pub(crate) iterator: JsValue, /// 本迭代器最近一次 DONE 返回的结果对象(CLOSE 判 done 用)。 pub(crate) last_result: JsValue, + /// 是否为 for-await-of 的异步迭代器:异步逃出关闭须 await return() 的 + /// promise(独立异步机制),同步逃出路径(break/continue/return 计数关闭) + /// 不得同步调用其 return()。 + pub(crate) is_async: bool, } /// for-in / for-of 的活跃迭代器状态。 @@ -154,10 +158,12 @@ impl IterState { } /// 压入新迭代器条目:`last_result` 初始为 undefined(尚未执行任何 next())。 - pub(crate) fn push_for_of(&mut self, iterator: JsValue) { + /// `is_async` 标记 for-await-of 的异步迭代器(异步逃出关闭走独立机制)。 + pub(crate) fn push_for_of(&mut self, iterator: JsValue, is_async: bool) { self.for_of_iters.push(ForOfEntry { iterator, last_result: JsValue::undefined(), + is_async, }); } diff --git a/crates/oxide_vm/tests/async_tests.rs b/crates/oxide_vm/tests/async_tests.rs index e3ffc49..cd4202a 100644 --- a/crates/oxide_vm/tests/async_tests.rs +++ b/crates/oxide_vm/tests/async_tests.rs @@ -104,7 +104,10 @@ fn async_throw_rejects_promise() { #[test] fn async_is_not_constructor() { - assert!(eval("new (async function(){})").starts_with("vm error") || eval("new (async function(){})").contains("not a constructor")); + assert!( + eval("new (async function(){})").starts_with("vm error") + || eval("new (async function(){})").contains("not a constructor") + ); } #[test] @@ -201,6 +204,35 @@ fn for_await_of_nested_sync_for_of_break_close_respective() { (async()=>{ for await (const x of asyncIter) { for (const y of sync) { break; } break; } })();\ Promise.resolve().then(()=>log.join(','))" ), - "\"s,a\"" + "\"s,a\"" + ); +} + +#[test] +fn for_await_of_body_throw_closes_and_original_error_wins() { + // for-await-of 循环体抛错:先调 return() 关闭迭代器,原错误优先传播给 catch。 + assert_eq!( + eval( + "let log=[];\ + const it={[Symbol.asyncIterator](){return{next(){return Promise.resolve({value:1,done:false})},return(){log.push('r');return Promise.resolve({done:true})}}}};\ + (async()=>{ try { for await (const x of it) { throw 'orig'; } } catch(e) { log.push('c:'+e); } return log.join(','); })()" + ), + "\"r,c:orig\"" + ); +} + +#[test] +fn for_await_of_return_escape_defers_async_close() { + // return 逃出 for-await-of:异步迭代器 return() 的 promise 须 await 后结算, + // 走异步关闭机制(后续阶段);阶段 1 同步逃出路径不得同步调用异步 return()。 + // 断言循环体已执行且没有同步副作用泄露。 + assert_eq!( + eval( + "let log=[];\ + const it={[Symbol.asyncIterator](){return{next(){return Promise.resolve({value:1,done:false})},return(){log.push('close');return Promise.resolve({done:true})}}}};\ + (async()=>{ for await (const x of it) { log.push('body'); return 1; } })();\ + Promise.resolve().then(()=>log.join(','))" + ), + "\"body\"" ); } diff --git a/crates/oxide_vm/tests/for_of_abrupt_completion_tests.rs b/crates/oxide_vm/tests/for_of_abrupt_completion_tests.rs index bee4429..2cf3ab9 100644 --- a/crates/oxide_vm/tests/for_of_abrupt_completion_tests.rs +++ b/crates/oxide_vm/tests/for_of_abrupt_completion_tests.rs @@ -153,3 +153,158 @@ fn for_of_nested_iterators_break_close_inner_only() { "true" ); } + +#[test] +fn for_of_return_escape_closes_iterator() { + // return 逃出 for-of:跳转前执行 IteratorClose(当前实现此前完全漏调)。 + assert_eq!( + eval( + "var closed=0;\ + var it={[Symbol.iterator](){return{next(){return{value:1,done:false}},return(){closed++;return{}}}}};\ + function f(){ for (const x of it) { return 1; } } f();closed===1" + ), + "true" + ); +} + +#[test] +fn for_of_labeled_break_escapes_all_in_lifo_order() { + // labeled break 逃出两层:按嵌套逆序(内层先)逐层关闭。 + assert_eq!( + eval( + "var log=[];\ + var mk=(n)=>({[Symbol.iterator](){return{next(){return{value:n,done:false}},return(){log.push(n);return{}}}}});\ + var it1=mk(1), it2=mk(2);\ + outer: for (const a of it1) { for (const b of it2) { break outer; } }\ + log.join(',')==='2,1'" + ), + "true" + ); +} + +#[test] +fn for_of_labeled_continue_closes_inner_only() { + // labeled continue 逃出:内层每轮关闭,外层循环继续迭代不关闭。 + assert_eq!( + eval( + "var log=[];\ + var mk=(n)=>({[Symbol.iterator](){return{next(){return{value:n,done:false}},return(){log.push(n);return{}}}}});\ + var it2=mk(2);\ + outer: for (const a of [1,2]) { for (const b of it2) { continue outer; } }\ + log.join(',')==='2,2'" + ), + "true" + ); +} + +#[test] +fn for_of_escape_finally_runs_before_close() { + // 规范顺序:循环体完成值(含内部 try-finally 处理)之后才执行 IteratorClose。 + assert_eq!( + eval( + "var log=[];\ + var it={[Symbol.iterator](){return{next(){return{value:1,done:false}},return(){log.push('close');return{}}}}};\ + function f(){ try { for (const x of it) { log.push('ret'); return 1; } } finally { log.push('finally'); } }\ + f();log.join(',')==='ret,finally,close'" + ), + "true" + ); +} + +#[test] +fn for_of_escape_return_error_supersedes_and_closes_rest() { + // 逃出时 return() 抛错:新错误替代完成值被外围 catch 捕获,剩余迭代器 + // (外层)仍被关闭(unwind 兜底,错误优先)。 + assert_eq!( + eval( + "var log=[];\ + var it={[Symbol.iterator](){return{next(){return{value:1,done:false}},return(){log.push('c1');return{}}}}};\ + var it2={[Symbol.iterator](){return{next(){return{value:1,done:false}},return(){log.push('c2');throw new Error('B');return{}}}}};\ + function f(){ try { for (var a of it) { for (var b of it2) { return 1; } } } catch(e) { log.push(e.message); } }\ + f();log.join(',')==='c2,c1,B'" + ), + "true" + ); +} + +#[test] +fn for_of_generator_return_escape_closes_iterators() { + // 生成器 .return() 注入时关闭挂起点打开的全部迭代器。 + assert_eq!( + eval( + "var closed=0;\ + var it={[Symbol.iterator](){return{next(){return{value:1,done:false}},return(){closed++;return{}}}}};\ + function* g(){ for (const x of it) { yield 1; return 2; } }\ + var gen=g();gen.next();gen.return(9);closed===1" + ), + "true" + ); +} + +#[test] +fn for_in_labeled_break_pops_iterator_stack() { + // labeled break 逃出 for-in:弹出内层迭代器,后续 for-in 从干净栈开始迭代 + // (此前逃出会使 for_in_iters 残留,后续 FOR_IN_DONE 读错 keys)。 + assert_eq!( + eval( + "var seen=[];\ + outer: for (var k in {a:1,b:2}) { for (var j in {c:3}) { break outer; } }\ + for (var m in {d:4}) { seen.push(m); }seen.join(',')==='d'" + ), + "true" + ); +} + +#[test] +fn for_of_while_break_keeps_outer_iterator() { + // while 内 break 不逃出外层 for-of:外层迭代器不被关闭(计数按词法深度对齐)。 + assert_eq!( + eval( + "var closed=0;\ + var it={[Symbol.iterator](){return{next(){return{value:1,done:false}},return(){closed++;return{}}}}};\ + for (var a of it) { while (true) { break; } break; }closed===1" + ), + "true" + ); +} + +#[test] +fn for_of_switch_break_keeps_outer_iterator() { + // switch 内 break 不逃出外层 for-of:只关当前循环自身。 + assert_eq!( + eval( + "var closed=0;\ + var it={[Symbol.iterator](){return{next(){return{value:1,done:false}},return(){closed++;return{}}}}};\ + for (var a of it) { switch (a) { case 1: break; } break; }closed===1" + ), + "true" + ); +} + +#[test] +fn for_of_switch_break_continues_body_then_closes_once() { + // switch 内 break 只跳出 switch:循环体后续语句继续执行,迭代器保持打开, + // 直到循环收尾 CLOSE 才关闭一次(此前误在 switch 出口处提前关闭)。 + assert_eq!( + eval( + "var log=[];\ + var it={[Symbol.iterator](){return{next(){return{value:1,done:false}},return(){log.push('close');return{}}}}};\ + for (var a of it) { switch (a) { case 1: break; } log.push('body'); break; }\ + log.join(',')==='body,close'" + ), + "true" + ); +} + +#[test] +fn for_of_labeled_block_break_closes_all() { + // label 包裹非循环语句:break 逃出整个 label 域,全部 for-of 关闭。 + assert_eq!( + eval( + "var closed=0;\ + var it={[Symbol.iterator](){return{next(){return{value:1,done:false}},return(){closed++;return{}}}}};\ + outer: { for (var a of it) { break outer; } }closed===1" + ), + "true" + ); +}