From ae73fb8da5e9453258ee8d7af488fa74ac6801e8 Mon Sep 17 00:00:00 2001 From: pjh456 <147148383@qq.com> Date: Fri, 9 Oct 2026 19:14:36 +0000 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=BC=82=E5=B8=B8=E4=B8=8E=20return=20?= =?UTF-8?q?=E7=A9=BF=E8=B6=8A=E9=87=8A=E6=94=BE=E8=B5=84=E6=BA=90=EF=BC=8C?= =?UTF-8?q?dispose=5Fcount=20=E9=97=A8=E6=8E=A7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Cargo.lock | 38 ++--- Cargo.toml | 2 +- crates/oxide_bytecode/src/module.rs | 7 +- crates/oxide_bytecode/src/opcode.rs | 17 +-- crates/oxide_emit/src/class/emit.rs | 2 +- crates/oxide_emit/src/emit_ctx.rs | 3 - crates/oxide_emit/src/function_body.rs | 6 +- crates/oxide_emit/src/module.rs | 6 +- .../src/stmt/basic/break_continue.rs | 131 ++++++++++-------- .../oxide_emit/src/stmt/basic/return_stmt.rs | 16 ++- crates/oxide_ir/src/inst.rs | 22 +-- crates/oxide_vm/src/generator.rs | 6 +- crates/oxide_vm/src/ic_helper.rs | 24 ++-- crates/oxide_vm/src/suspended.rs | 1 + crates/oxide_vm/src/vm/frames.rs | 26 +++- crates/oxide_vm/src/vm_dispatch_ctrl.rs | 33 +++-- crates/oxide_vm/src/vm_dispatch_misc.rs | 51 +++++++ crates/oxide_vm/src/vm_runtime.rs | 20 +-- crates/oxide_vm/src/vm_support/tests.rs | 1 + crates/oxide_vm/tests/spill_stack_tests.rs | 4 +- 20 files changed, 264 insertions(+), 152 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 06152b16..f5be0bd3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1160,7 +1160,7 @@ dependencies = [ [[package]] name = "oxide_builtins" -version = "0.13.16" +version = "0.13.17" dependencies = [ "chrono", "fast-float", @@ -1185,7 +1185,7 @@ dependencies = [ [[package]] name = "oxide_bytecode" -version = "0.13.16" +version = "0.13.17" dependencies = [ "num-bigint", "oxide_log", @@ -1193,7 +1193,7 @@ dependencies = [ [[package]] name = "oxide_cfg" -version = "0.13.16" +version = "0.13.17" dependencies = [ "oxide_bytecode", "oxide_emit", @@ -1204,7 +1204,7 @@ dependencies = [ [[package]] name = "oxide_cli" -version = "0.13.16" +version = "0.13.17" dependencies = [ "ansi_term", "bumpalo", @@ -1226,7 +1226,7 @@ dependencies = [ [[package]] name = "oxide_code_cache" -version = "0.13.16" +version = "0.13.17" dependencies = [ "lru", "oxide_bytecode", @@ -1235,7 +1235,7 @@ dependencies = [ [[package]] name = "oxide_compiler" -version = "0.13.16" +version = "0.13.17" dependencies = [ "oxide_bytecode", "oxide_cfg", @@ -1255,7 +1255,7 @@ dependencies = [ [[package]] name = "oxide_dce" -version = "0.13.16" +version = "0.13.17" dependencies = [ "oxide_bytecode", "oxide_cfg", @@ -1269,7 +1269,7 @@ dependencies = [ [[package]] name = "oxide_emit" -version = "0.13.16" +version = "0.13.17" dependencies = [ "num-bigint", "oxc_allocator", @@ -1284,7 +1284,7 @@ dependencies = [ [[package]] name = "oxide_engine" -version = "0.13.16" +version = "0.13.17" dependencies = [ "oxide_bytecode", "oxide_compiler", @@ -1296,7 +1296,7 @@ dependencies = [ [[package]] name = "oxide_ir" -version = "0.13.16" +version = "0.13.17" dependencies = [ "oxide_bytecode", "oxide_log", @@ -1305,7 +1305,7 @@ dependencies = [ [[package]] name = "oxide_kernel" -version = "0.13.16" +version = "0.13.17" dependencies = [ "bumpalo", "dashmap", @@ -1319,7 +1319,7 @@ dependencies = [ [[package]] name = "oxide_liveness" -version = "0.13.16" +version = "0.13.17" dependencies = [ "oxide_bytecode", "oxide_cfg", @@ -1331,7 +1331,7 @@ dependencies = [ [[package]] name = "oxide_log" -version = "0.13.16" +version = "0.13.17" dependencies = [ "tracing", "tracing-appender", @@ -1340,7 +1340,7 @@ dependencies = [ [[package]] name = "oxide_parser" -version = "0.13.16" +version = "0.13.17" dependencies = [ "oxc_allocator", "oxc_ast", @@ -1355,7 +1355,7 @@ dependencies = [ [[package]] name = "oxide_regalloc" -version = "0.13.16" +version = "0.13.17" dependencies = [ "oxide_bytecode", "oxide_cfg", @@ -1371,7 +1371,7 @@ dependencies = [ [[package]] name = "oxide_runtime_api" -version = "0.13.16" +version = "0.13.17" dependencies = [ "num-bigint", "num-traits", @@ -1384,7 +1384,7 @@ dependencies = [ [[package]] name = "oxide_test262" -version = "0.13.16" +version = "0.13.17" dependencies = [ "oxide_bytecode", "oxide_compiler", @@ -1402,7 +1402,7 @@ dependencies = [ [[package]] name = "oxide_types" -version = "0.13.16" +version = "0.13.17" dependencies = [ "bumpalo", "dashmap", @@ -1415,7 +1415,7 @@ dependencies = [ [[package]] name = "oxide_vm" -version = "0.13.16" +version = "0.13.17" dependencies = [ "bumpalo", "chrono", diff --git a/Cargo.toml b/Cargo.toml index 04f208b3..a0d454d8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,7 +23,7 @@ members = [ ] [workspace.package] -version = "0.13.16" +version = "0.13.17" edition = "2021" license = "MIT" rust-version = "1.80" diff --git a/crates/oxide_bytecode/src/module.rs b/crates/oxide_bytecode/src/module.rs index f39c8e60..f554e094 100644 --- a/crates/oxide_bytecode/src/module.rs +++ b/crates/oxide_bytecode/src/module.rs @@ -347,7 +347,7 @@ mod tests { use super::*; /// 手工构造反汇编夹具:顶层 5 条指令(NEW_OBJECT 1 字键表、CALL 1 ext 字、 - /// IC_GET_PROP 8 ext 字),子模块 1 个(3 条指令,RETURN 带 1 个逃出计数 ext 字)。 + /// IC_GET_PROP 8 ext 字),子模块 1 个(3 条指令,RETURN 带 2 个逃出计数 ext 字)。 fn fixture() -> CompiledModule { let sub = CompiledModule { function_name: Some("f".into()), @@ -357,7 +357,8 @@ mod tests { bytecode: Arc::from([ opcode::encode(OpCode::LOAD_CONST, 1, 0, 0), opcode::encode(OpCode::RETURN, 1, 0, 0), - 0, // RETURN 逃出计数 ext 字 + 0, // RETURN 迭代器打包 ext 字 + 0, // RETURN dispose_count ext 字 opcode::encode(OpCode::HALT, 0, 0, 0), ]), ..Default::default() @@ -404,7 +405,7 @@ mod tests { " 0014 HALT r0", " 0000 LOAD_CONST r1, const[0]", " 0001 RETURN r1", - " 0003 HALT r0", + " 0004 HALT r0", ]; for line in &expected { assert!(lines.contains(line), "missing line: {line}\n{text}"); diff --git a/crates/oxide_bytecode/src/opcode.rs b/crates/oxide_bytecode/src/opcode.rs index 37702d06..8e7f33c3 100644 --- a/crates/oxide_bytecode/src/opcode.rs +++ b/crates/oxide_bytecode/src/opcode.rs @@ -824,10 +824,11 @@ pub fn ext_word_count(bytecode: &[Instr], pc: usize) -> usize { | OpCode::GET_PRIVATE | OpCode::SET_PRIVATE | OpCode::PRIVATE_BRAND_IN => 2, - // 逃出计数 ext:BREAK/CONTINUE/RETURN 恒带 1 个 pack_escape_counts 字 - // (for-of/for-in 逃出层数打包);lower 对这三条无条件落 ext 字, - // dispatch 经 read_escape_counts 消费,扫描必须同步跳过以免错位。 - OpCode::BREAK | OpCode::CONTINUE | OpCode::RETURN => 1, + // 逃出计数 ext:BREAK/CONTINUE/RETURN 恒带 2 个字——第一字 + // pack_escape_counts(for-of/for-in 逃出层数打包),第二字 dispose_count + // (逃出资源条数);lower 对这三条无条件落 ext 字,dispatch 经 + // read_escape_counts 消费,扫描必须同步跳过以免错位。 + OpCode::BREAK | OpCode::CONTINUE | OpCode::RETURN => 2, OpCode::CALL_SPREAD | OpCode::NEW_EXPRESSION_SPREAD | OpCode::SUPER_CALL_SPREAD => { let header = bytecode.get(pc + 1).copied().unwrap_or(0); 1 + (header & 0xFF) as usize + ((header >> 8) & 0xFF) as usize @@ -972,11 +973,11 @@ mod tests { // IC 族固定 8 字。 let ic = [encode(OpCode::IC_GET_PROP, 0, 0, 0); IC_EXT_WORDS + 1]; assert_eq!(ext_word_count(&ic, 0), IC_EXT_WORDS); - // 1 字族(含 BREAK/CONTINUE/RETURN 逃出计数)。 + // 1 字族。 assert_eq!(ext_word_count(&[encode(OpCode::CALL, 0, 0, 0), 3], 0), 1); - assert_eq!(ext_word_count(&[encode(OpCode::BREAK, 0, 0, 0), 1], 0), 1); - assert_eq!(ext_word_count(&[encode(OpCode::RETURN, 0, 0, 0), 1], 0), 1); - // 2 字族。 + // 2 字族(含 BREAK/CONTINUE/RETURN 逃出计数:迭代器打包字 + dispose_count 字)。 + assert_eq!(ext_word_count(&[encode(OpCode::BREAK, 0, 0, 0), 1, 2], 0), 2); + assert_eq!(ext_word_count(&[encode(OpCode::RETURN, 0, 0, 0), 1, 2], 0), 2); assert_eq!(ext_word_count(&[encode(OpCode::GET_PRIVATE, 0, 0, 0), 1, 2], 0), 2); // 变长族:spread 首字 nstatic|nspread、TEMPLATE_STR 段数、GET_TEMPLATE_OBJECT 2+2n、 // CONCAT_N=n、NEW_OBJECT=a 槽属性数。 diff --git a/crates/oxide_emit/src/class/emit.rs b/crates/oxide_emit/src/class/emit.rs index bf3cbc3d..369b6fc2 100644 --- a/crates/oxide_emit/src/class/emit.rs +++ b/crates/oxide_emit/src/class/emit.rs @@ -316,7 +316,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::ret(Operand::Reg(undef_reg), 0, 0)); + module.insts.push(Inst::ret(Operand::Reg(undef_reg), 0, 0, 0)); } module }; diff --git a/crates/oxide_emit/src/emit_ctx.rs b/crates/oxide_emit/src/emit_ctx.rs index 2d79917d..0394a787 100644 --- a/crates/oxide_emit/src/emit_ctx.rs +++ b/crates/oxide_emit/src/emit_ctx.rs @@ -50,7 +50,6 @@ pub(crate) struct LoopEntry { pub(crate) v_reg: u32, /// 循环打开时已打开的释放作用域数(break/continue/return 逃出时据此 /// 计算需穿越释放的作用域层数)。 - #[allow(dead_code)] pub(crate) dispose_depth_at_open: usize, } @@ -88,7 +87,6 @@ pub struct LabelScope { /// 标签打开时已打开的 for-in 循环数。 pub(crate) for_in_depth_at_open: usize, /// 标签打开时已打开的释放作用域数(break 逃出时据此计算需穿越释放的层数)。 - #[allow(dead_code)] pub(crate) dispose_depth_at_open: usize, /// 标签目标出口结果寄存器:迭代标签取所包裹循环的 `v_reg`;非迭代标签仅当 /// 体含指向本标签的 break 时分配(体正常完成回写、break 携值写入)。 @@ -104,7 +102,6 @@ pub(crate) struct SwitchEntry { pub(crate) for_of_depth_at_open: usize, pub(crate) for_in_depth_at_open: usize, /// switch 打开时已打开的释放作用域数(switch 内 break 逃出时据此计算穿越层数)。 - #[allow(dead_code)] pub(crate) dispose_depth_at_open: usize, pub(crate) result_reg: u32, } diff --git a/crates/oxide_emit/src/function_body.rs b/crates/oxide_emit/src/function_body.rs index 5143e19a..21cf816f 100644 --- a/crates/oxide_emit/src/function_body.rs +++ b/crates/oxide_emit/src/function_body.rs @@ -509,18 +509,18 @@ impl Emitter { } if is_expression_body { if let Some(reg) = last_result_reg { - ctx.inst(Inst::ret(Operand::Reg(reg), 0, 0)); + ctx.inst(Inst::ret(Operand::Reg(reg), 0, 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::ret(Operand::Reg(undef_reg), 0, 0)); + ctx.inst(Inst::ret(Operand::Reg(undef_reg), 0, 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::ret(Operand::Reg(undef_reg), 0, 0)); + ctx.inst(Inst::ret(Operand::Reg(undef_reg), 0, 0, 0)); } // 调用契约参数段只含固定形参:rest 是函数体内普通变量,不在 VM 实参传递区。 diff --git a/crates/oxide_emit/src/module.rs b/crates/oxide_emit/src/module.rs index ba695081..7b57a148 100644 --- a/crates/oxide_emit/src/module.rs +++ b/crates/oxide_emit/src/module.rs @@ -938,9 +938,9 @@ impl Emitter { let undef_idx = ctx.add_constant(Constant::Undefined); let r = ctx.alloc_reg(); ctx.inst(Inst::load_const(Operand::Reg(r), undef_idx)); - ctx.inst(Inst::ret(Operand::Reg(r), 0, 0)); + ctx.inst(Inst::ret(Operand::Reg(r), 0, 0, 0)); } else { - ctx.inst(Inst::ret(Operand::Reg(ns_reg), 0, 0)); + ctx.inst(Inst::ret(Operand::Reg(ns_reg), 0, 0, 0)); } Ok(()) } @@ -1003,7 +1003,7 @@ 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::ret(Operand::Reg(val_reg), 0, 0)); + ctx.inst(Inst::ret(Operand::Reg(val_reg), 0, 0, 0)); Ok(ctx.assemble_ir(ParamLayout { base: 0, count: 0 }, None)) } diff --git a/crates/oxide_emit/src/stmt/basic/break_continue.rs b/crates/oxide_emit/src/stmt/basic/break_continue.rs index 51dd5e48..ee800478 100644 --- a/crates/oxide_emit/src/stmt/basic/break_continue.rs +++ b/crates/oxide_emit/src/stmt/basic/break_continue.rs @@ -33,46 +33,51 @@ impl Emitter { pub(crate) fn emit_break_statement( &self, stmt: &oxide_parser::BreakStatement, ctx: &mut CompileCtx, ) -> Result, String> { - let (break_label, fd_at_open, fod_at_open, fid_at_open, target_v_reg) = 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, - scope.for_of_depth_at_open, - scope.for_in_depth_at_open, - scope.completion_reg, - ) - } else if let Some(sw) = ctx.current_switch() { - // switch 内 break:目标为 switch 出口。逃出计数以 switch 打开时的 - // 深度快照为基准——switch 之前已打开的循环不属于本次逃出,仅当 - // case 内新打开了迭代循环(并逃出)时才需关闭。 - ( - sw.break_label, - sw.finally_depth_at_open, - sw.for_of_depth_at_open, - sw.for_in_depth_at_open, - Some(sw.result_reg), - ) - } else { - 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, - Some(entry.v_reg), - ) - }; + let (break_label, fd_at_open, fod_at_open, fid_at_open, dd_at_open, target_v_reg) = + 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, + scope.for_of_depth_at_open, + scope.for_in_depth_at_open, + scope.dispose_depth_at_open, + scope.completion_reg, + ) + } else if let Some(sw) = ctx.current_switch() { + // switch 内 break:目标为 switch 出口。逃出计数以 switch 打开时的 + // 深度快照为基准——switch 之前已打开的循环不属于本次逃出,仅当 + // case 内新打开了迭代循环(并逃出)时才需关闭。 + ( + sw.break_label, + sw.finally_depth_at_open, + sw.for_of_depth_at_open, + sw.for_in_depth_at_open, + sw.dispose_depth_at_open, + Some(sw.result_reg), + ) + } else { + 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, + entry.dispose_depth_at_open, + Some(entry.v_reg), + ) + }; if let Some(target_v_reg) = target_v_reg { self.emit_completion_carry(ctx, target_v_reg); } let crossed = ctx.labels.finally_depth.saturating_sub(fd_at_open) as u16; 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)); + let dispose_count = ctx.labels.dispose_depth.saturating_sub(dd_at_open); + if crossed > 0 || for_of_count > 0 || for_in_count > 0 || dispose_count > 0 { + ctx.inst(Inst::brk(break_label, crossed, for_of_count, for_in_count, dispose_count)); } else { ctx.inst(Inst::jmp(break_label)); } @@ -82,37 +87,41 @@ impl Emitter { pub(crate) fn emit_continue_statement( &self, stmt: &oxide_parser::ContinueStatement, ctx: &mut CompileCtx, ) -> Result, String> { - let (continue_label, fd_at_open, fod_at_open, fid_at_open, target_v_reg) = 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.continue_label.ok_or_else(|| { - 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, - scope.completion_reg, - ) - } else { - 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, - Some(entry.v_reg), - ) - }; + let (continue_label, fd_at_open, fod_at_open, fid_at_open, dd_at_open, target_v_reg) = + 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.continue_label.ok_or_else(|| { + 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, + scope.dispose_depth_at_open, + scope.completion_reg, + ) + } else { + 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, + entry.dispose_depth_at_open, + Some(entry.v_reg), + ) + }; if let Some(target_v_reg) = target_v_reg { self.emit_completion_carry(ctx, target_v_reg); } let crossed = ctx.labels.finally_depth.saturating_sub(fd_at_open) as u16; 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)); + let dispose_count = ctx.labels.dispose_depth.saturating_sub(dd_at_open); + if crossed > 0 || for_of_count > 0 || for_in_count > 0 || dispose_count > 0 { + ctx.inst(Inst::cont(continue_label, crossed, for_of_count, for_in_count, dispose_count)); } else { ctx.inst(Inst::jmp(continue_label)); } diff --git a/crates/oxide_emit/src/stmt/basic/return_stmt.rs b/crates/oxide_emit/src/stmt/basic/return_stmt.rs index 23f38009..be324497 100644 --- a/crates/oxide_emit/src/stmt/basic/return_stmt.rs +++ b/crates/oxide_emit/src/stmt/basic/return_stmt.rs @@ -12,9 +12,11 @@ impl Emitter { let Statement::ReturnStatement(ret) = stmt else { return Ok(None); }; - // return 逃出全部打开的迭代循环:for-of 需 IteratorClose,for-in 弹出迭代器。 + // return 逃出全部打开的迭代循环与释放作用域:for-of 需 IteratorClose, + // for-in 弹出迭代器,释放栈顶部资源需 DisposeResources。 let for_of_count = ctx.labels.for_of_depth; let for_in_count = ctx.labels.for_in_depth; + let dispose_count = ctx.labels.dispose_depth; match &ret.argument { Some(expr) => { let r = self.emit_expression(expr, ctx)?; @@ -22,15 +24,15 @@ impl Emitter { // 弹出连续纯 catch handler(finally handler 走运行时完成穿越逐个 // 执行)。否则 return 跳过 TRY_END 会让 handler 残留在 try_stack, // 后续异常 unwind 会跳回已返回函数的 catch 形成死循环。 - // 逃出迭代器时(close_count>0)保留 handler:return() 抛错须经 - // unwind 被外围 catch 捕获(新错误替代完成值),弹出后无法展开。 - if for_of_count == 0 && for_in_count == 0 { + // 逃出迭代器或资源时(close_count>0)保留 handler:return() 抛错 + // 须经 unwind 被外围 catch 捕获(新错误替代完成值),弹出后无法展开。 + if for_of_count == 0 && for_in_count == 0 && dispose_count == 0 { self.emit_return_try_cleanup(ctx); } - ctx.inst(Inst::ret(Operand::Reg(r), for_of_count, for_in_count)); + ctx.inst(Inst::ret(Operand::Reg(r), for_of_count, for_in_count, dispose_count)); } None => { - if for_of_count == 0 && for_in_count == 0 { + if for_of_count == 0 && for_in_count == 0 && dispose_count == 0 { self.emit_return_try_cleanup(ctx); } // 裸 return 须显式物化 undefined 为完成值:RETURN 从 rd 槽读值, @@ -38,7 +40,7 @@ impl Emitter { 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::ret(Operand::Reg(undef_reg), for_of_count, for_in_count)); + ctx.inst(Inst::ret(Operand::Reg(undef_reg), for_of_count, for_in_count, dispose_count)); } } Ok(None) diff --git a/crates/oxide_ir/src/inst.rs b/crates/oxide_ir/src/inst.rs index 8b62c7e7..712bea78 100644 --- a/crates/oxide_ir/src/inst.rs +++ b/crates/oxide_ir/src/inst.rs @@ -447,39 +447,41 @@ impl Inst { /// break 完成:label 指向循环/switch 出口,`crossed` 为逃出的 finally 域数 /// (emit 词法计算,运行时据此逐个穿越 finally;0 表示未逃出,不产生完成语义)。 - /// `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 { + /// `for_of_count`/`for_in_count` 为逃出的迭代器层数,`dispose_count` 为逃出的 + /// 资源条数,编码进 ext 字——BREAK/CONTINUE 的 a/b 槽被跳转 offset 占用,无空闲位。 + /// 编码:rd 槽放 crossed(≤255),label 放 b 槽,ext[0] 打包两个迭代器计数, + /// ext[1] 放 dispose_count。 + pub fn brk(label: LabelId, crossed: u16, for_of_count: usize, for_in_count: usize, dispose_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)], + &[pack_escape_counts(for_of_count, for_in_count), dispose_count as u32], ) } /// continue 完成:同 break,label 指向循环继续目标。 - pub fn cont(label: LabelId, crossed: u16, for_of_count: usize, for_in_count: usize) -> Self { + pub fn cont(label: LabelId, crossed: u16, for_of_count: usize, for_in_count: usize, dispose_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)], + &[pack_escape_counts(for_of_count, for_in_count), dispose_count as u32], ) } /// 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 { + /// `for_of_count`/`for_in_count` 为逃出的迭代器层数,`dispose_count` 为逃出的 + /// 资源条数,编码进 ext 字。 + pub fn ret(src: Operand, for_of_count: usize, for_in_count: usize, dispose_count: usize) -> Self { Self::with_ext( OpCode::RETURN, src, Operand::None, Operand::None, - &[pack_escape_counts(for_of_count, for_in_count)], + &[pack_escape_counts(for_of_count, for_in_count), dispose_count as u32], ) } diff --git a/crates/oxide_vm/src/generator.rs b/crates/oxide_vm/src/generator.rs index 63a910fb..55690c06 100644 --- a/crates/oxide_vm/src/generator.rs +++ b/crates/oxide_vm/src/generator.rs @@ -723,15 +723,17 @@ impl Vm { .iter() .filter(|h| h.frame_depth == self.frames.len() && h.finally_pc.is_some()) .count(); - // .return()/.throw() 注入时生成器挂起快照中打开的全部迭代器一并逃出 - // (栈上迭代器属于本生成器),完成恢复处统一关闭。 + // .return()/.throw() 注入时生成器挂起快照中打开的全部迭代器与资源一并逃出 + // (栈上迭代器/资源属于本生成器),完成恢复处统一关闭/释放。 let for_of_count = self.iters.for_of_iters.len(); let for_in_count = self.iters.for_in_iters.len(); + let dispose_count = self.dispose_stack.len(); let completion = crate::vm::Completion::Return { value, remaining_finally: crossed, for_of_count, for_in_count, + dispose_count, }; if let Some(finally_pc) = self.record_completion(completion) { self.pc = finally_pc; diff --git a/crates/oxide_vm/src/ic_helper.rs b/crates/oxide_vm/src/ic_helper.rs index 7ef50837..55888ccb 100644 --- a/crates/oxide_vm/src/ic_helper.rs +++ b/crates/oxide_vm/src/ic_helper.rs @@ -285,16 +285,19 @@ mod tests { } #[test] - fn break_ret_ext_word_count_is_one() { - // BREAK/CONTINUE/RETURN 恒带 1 个逃出计数 ext 字(pack_escape_counts)。 + fn break_ret_ext_word_count_is_two() { + // BREAK/CONTINUE/RETURN 恒带 2 个逃出计数 ext 字:第一字 + // pack_escape_counts(迭代器层数打包),第二字 dispose_count(资源条数)。 let bytecode = vec![ opcode::encode(OpCode::BREAK, 0, 0, 0), 0x0000_0040, + 0x0000_0002, opcode::encode(OpCode::RETURN, 1, 0, 0), 0x1234_5678, + 0x0000_0003, ]; - assert_eq!(ext_word_count(&bytecode, 0), 1, "BREAK ext 字数 = 1"); - assert_eq!(ext_word_count(&bytecode, 2), 1, "RETURN ext 字数 = 1"); + assert_eq!(ext_word_count(&bytecode, 0), 2, "BREAK ext 字数 = 2"); + assert_eq!(ext_word_count(&bytecode, 3), 2, "RETURN ext 字数 = 2"); } #[test] @@ -331,12 +334,14 @@ mod tests { #[test] fn clear_ic_caches_skips_break_ext_word() { - // BREAK 的逃出计数 ext 字必须原样保留;其低 8 位 = for_of_count,取 0x40 - // (CALL 指令号,带 ext)——未登记时扫描会把它当 CALL 指令解析,越过后续 - // IC 指令导致扩展字漏清,本用例可抓住该错位。 + // BREAK 的逃出计数 ext 字(2 个:迭代器打包字 + dispose_count 字)必须原样 + // 保留;第一字低 8 位 = for_of_count,取 0x40(CALL 指令号,带 ext)——未 + // 登记时扫描会把它当 CALL 指令解析,越过后续 IC 指令导致扩展字漏清,本用例 + // 可抓住该错位。 let mut bytecode = vec![ opcode::encode(OpCode::BREAK, 0, 0, 0), 0x0000_0040, + 0x0000_0002, opcode::encode(OpCode::IC_GET_PROP, 1, 2, 3), 0xAAAA_AAAA, 0xBBBB_BBBB, @@ -348,8 +353,9 @@ mod tests { 0x2222_2222, ]; clear_ic_caches(&mut bytecode); - assert_eq!(bytecode[1], 0x0000_0040, "BREAK ext 字保持原值"); - assert_eq!(&bytecode[3..=10], &[0; 8], "后续 IC 扩展字被清零"); + assert_eq!(bytecode[1], 0x0000_0040, "BREAK 第一 ext 字保持原值"); + assert_eq!(bytecode[2], 0x0000_0002, "BREAK 第二 ext 字保持原值"); + assert_eq!(&bytecode[4..=11], &[0; 8], "后续 IC 扩展字被清零"); } #[test] diff --git a/crates/oxide_vm/src/suspended.rs b/crates/oxide_vm/src/suspended.rs index a9455fe1..9f016fc4 100644 --- a/crates/oxide_vm/src/suspended.rs +++ b/crates/oxide_vm/src/suspended.rs @@ -312,6 +312,7 @@ mod tests { remaining_finally: 1, for_of_count: 0, for_in_count: 0, + dispose_count: 0, }); let mut frame = SuspendedFrame::new_empty(); diff --git a/crates/oxide_vm/src/vm/frames.rs b/crates/oxide_vm/src/vm/frames.rs index 353b0eed..288a63d9 100644 --- a/crates/oxide_vm/src/vm/frames.rs +++ b/crates/oxide_vm/src/vm/frames.rs @@ -109,14 +109,18 @@ pub struct TryHandler { pub frame_depth: usize, /// try 入口时 for_of_iters 的长度,界定异常展开时哪些迭代器需要 IteratorClose。 pub for_of_depth: usize, + /// try 入口时释放栈(dispose_stack)的长度,界定异常展开时哪些资源需要 + /// DisposeResources(逆序释放并截断到本水位)。 + pub dispose_depth: usize, } /// 控制流完成:break/continue/return 逃出 finally 域时暂存的完成目标。 /// /// 仿 `pending_exception` 的侧通道:finally 执行期间悬挂在此,由 TRY_FINALLY_END /// 逐个恢复(`remaining_finally` 为仍需穿越的 finally 体数,进入一个递减一个)。 -/// `for_of_count`/`for_in_count` 为逃出时需关闭的迭代器层数:在全部 finally 穿越 -/// 之后、跳转/返回之前执行(规范 §13.7.5.4 的 IteratorClose 在完成值之后)。 +/// `for_of_count`/`for_in_count` 为逃出时需关闭的迭代器层数,`dispose_count` 为 +/// 逃出时需释放的资源条数:在全部 finally 穿越之后、跳转/返回之前执行(规范序 +/// IteratorClose 先、DisposeResources 后)。 #[derive(Debug, Clone, Copy)] pub enum Completion { Break { @@ -124,18 +128,21 @@ pub enum Completion { remaining_finally: usize, for_of_count: usize, for_in_count: usize, + dispose_count: usize, }, Continue { target_pc: usize, remaining_finally: usize, for_of_count: usize, for_in_count: usize, + dispose_count: usize, }, Return { value: JsValue, remaining_finally: usize, for_of_count: usize, for_in_count: usize, + dispose_count: usize, }, } @@ -167,6 +174,15 @@ impl Completion { } } + /// 逃出时需释放的资源条数(释放栈顶部连续条数)。 + pub fn dispose_count(&self) -> usize { + match *self { + Completion::Break { dispose_count, .. } + | Completion::Continue { dispose_count, .. } + | Completion::Return { dispose_count, .. } => dispose_count, + } + } + /// 复制并改写剩余 finally 计数(进入一个 finally 后递减)。 pub fn with_remaining(&self, remaining: usize) -> Completion { match *self { @@ -174,34 +190,40 @@ impl Completion { target_pc, for_of_count, for_in_count, + dispose_count, .. } => Completion::Break { target_pc, remaining_finally: remaining, for_of_count, for_in_count, + dispose_count, }, Completion::Continue { target_pc, for_of_count, for_in_count, + dispose_count, .. } => Completion::Continue { target_pc, remaining_finally: remaining, for_of_count, for_in_count, + dispose_count, }, Completion::Return { value, for_of_count, for_in_count, + dispose_count, .. } => Completion::Return { value, remaining_finally: remaining, for_of_count, for_in_count, + dispose_count, }, } } diff --git a/crates/oxide_vm/src/vm_dispatch_ctrl.rs b/crates/oxide_vm/src/vm_dispatch_ctrl.rs index 5863dc1a..cbab6942 100644 --- a/crates/oxide_vm/src/vm_dispatch_ctrl.rs +++ b/crates/oxide_vm/src/vm_dispatch_ctrl.rs @@ -631,7 +631,7 @@ impl Vm { /// 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 (for_of_count, for_in_count, dispose_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; @@ -640,6 +640,7 @@ impl Vm { remaining_finally: crossed, for_of_count, for_in_count, + dispose_count, }; if let Some(finally_pc) = self.record_completion(completion) { self.pc = finally_pc; @@ -658,7 +659,7 @@ impl Vm { /// continue 完成:同 break,目标为循环继续位置。 pub(crate) fn dispatch_continue(&mut self, instr: u32) -> Result<(), String> { - let (for_of_count, for_in_count) = self.read_escape_counts(); + let (for_of_count, for_in_count, dispose_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; @@ -667,6 +668,7 @@ impl Vm { remaining_finally: crossed, for_of_count, for_in_count, + dispose_count, }; if let Some(finally_pc) = self.record_completion(completion) { self.pc = finally_pc; @@ -683,19 +685,21 @@ impl Vm { Ok(()) } - /// 读取 BREAK/CONTINUE/RETURN 的 ext 字逃出计数(低 16 位 for-of,高 16 位 - /// for-in)。调用时机:主循环已把 pc 推进到 ext 字位置;两条跳转/返回路径 - /// 都会覆盖 pc,此处无需再推进。 + /// 读取 BREAK/CONTINUE/RETURN 的 ext 字逃出计数:第一字低 16 位 for-of、 + /// 高 16 位 for-in,第二字为 dispose_count(逃出资源条数)。调用时机:主循环 + /// 已把 pc 推进到 ext 字位置;两条跳转/返回路径都会覆盖 pc,此处无需再推进。 /// /// # 边界与前提 /// - 手工构造的 IR 可能让 RETURN/BREAK/CONTINUE 落在末位且无 ext 字(pc == len), - /// 此时按 (0, 0) 处理:无迭代器逃出,语义正确。 - fn read_escape_counts(&self) -> (usize, usize) { + /// 此时按 (0, 0, 0) 处理:无迭代器/资源逃出,语义正确。第二字缺失(pc+1 == len) + /// 同样按 0 兜底。 + fn read_escape_counts(&self) -> (usize, usize, usize) { if self.pc >= self.bytecode.len() { - return (0, 0); + return (0, 0, 0); } let packed = self.bytecode[self.pc]; - ((packed & 0xFFFF) as usize, (packed >> 16) as usize) + let dispose = self.bytecode.get(self.pc + 1).copied().unwrap_or(0) as usize; + ((packed & 0xFFFF) as usize, (packed >> 16) as usize, dispose) } /// 记录一次控制流完成:穿越 `remaining_finally` 个 finally 体后执行完成本身。 @@ -752,7 +756,7 @@ impl Vm { 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(); + let (for_of_count, for_in_count, dispose_count) = self.read_escape_counts(); crate::vm_debug!( "RETURN depth={} saved_pc={}", self.frames.len(), @@ -772,6 +776,7 @@ impl Vm { remaining_finally: crossed, for_of_count, for_in_count, + dispose_count, }; if let Some(finally_pc) = self.record_completion(completion) { self.pc = finally_pc; @@ -903,6 +908,7 @@ impl Vm { finally_active: false, frame_depth: self.frames.len(), for_of_depth: self.iters.for_of_iters.len(), + dispose_depth: self.dispose_stack.len(), }); } @@ -921,6 +927,7 @@ impl Vm { finally_active: false, frame_depth: self.frames.len(), for_of_depth: self.iters.for_of_iters.len(), + dispose_depth: self.dispose_stack.len(), }); } @@ -1029,6 +1036,12 @@ impl Vm { return Ok(CloseEscapeOutcome::Handled); } } + // 逃出资源释放(规范序:IteratorClose 先、DisposeResources 后):逆序释放 + // 顶部 dispose_count 条。释放方法抛错时新错误替代完成值,展开到外围处理器。 + if self.dispose_top_n(completion.dispose_count()) { + self.unwind()?; + return Ok(CloseEscapeOutcome::Handled); + } // 异步条目逐层关闭:首个有 return 方法的登记挂起,其余入 remaining;无 // return 方法时继续下一层。 let mut remaining = async_iters; diff --git a/crates/oxide_vm/src/vm_dispatch_misc.rs b/crates/oxide_vm/src/vm_dispatch_misc.rs index 704e5e89..9e684924 100644 --- a/crates/oxide_vm/src/vm_dispatch_misc.rs +++ b/crates/oxide_vm/src/vm_dispatch_misc.rs @@ -689,6 +689,57 @@ impl Vm { Ok(()) } + /// 逆序释放 `depth` 以上资源并截断释放栈到 `depth`,同步弹出水位栈中 + /// `>= depth` 的基线条目(保两栈一致,防异常路径基线泄漏)。 + /// + /// # 步骤 + /// 1. 逐条弹出并释放(`dispose_value`)。 + /// 2. 释放方法抛错时,新错误替代在途异常向外传播(`exception_value` 改写), + /// 剩余资源继续释放(其错误被抑制,多错合并归后续任务)。 + /// 3. 截断释放栈后,弹出水位栈中 `>= depth` 的基线条目。 + /// + /// # 返回值 + /// `true` = 有释放方法抛错(新错误已写入 `exception_value`);`false` = 全部正常。 + /// + /// # 副作用 + /// - 释放栈截断到 `depth`、水位栈弹出 `>= depth` 的基线。 + /// - 有释放方法抛错时改写 `exception_value`/`pending_error_kind`。 + pub(crate) fn dispose_above(&mut self, depth: usize) -> bool { + let mut threw = false; + let truncated = self.dispose_stack.len() > depth; + while self.dispose_stack.len() > depth { + let (value, hint) = self.dispose_stack.pop().expect("水位以上必有条目"); + if let Err(e) = self.dispose_value(value, hint) { + let exc = self + .last_uncaught_value + .take() + .unwrap_or_else(|| oxide_builtins::error::create_from_text(self, &e)); + let kind = self.thrown_error_kind(exc); + self.exception_value = Some(exc); + self.pending_error_kind = Some(kind); + threw = true; + } + } + // 仅在实际截断释放栈时同步弹基线,防 no-op 调用(如 dispose_top_n(0)) + // 误弹外层作用域的水位。 + if truncated { + while self.dispose_marks.last().is_some_and(|&m| m >= depth) { + self.dispose_marks.pop(); + } + } + threw + } + + /// 释放栈顶部 `n` 条资源(逃出释放:return/break/continue 完成值逃出作用域时 + /// 逆序释放被逃出的资源)。`n` 超过栈长时按全量释放。 + /// + /// # 返回值 + /// 同 `dispose_above`:`true` = 有释放方法抛错。 + pub(crate) fn dispose_top_n(&mut self, n: usize) -> bool { + let depth = self.dispose_stack.len().saturating_sub(n); + self.dispose_above(depth) + } + /// 单资源释放(规范 Dispose 语义):非对象跳过;按释放提示取方法(同步只取 /// Symbol.dispose;异步先取 Symbol.asyncDispose,未定义回退 Symbol.dispose), /// 不可调用跳过;以资源自身为 this 零参调用。 diff --git a/crates/oxide_vm/src/vm_runtime.rs b/crates/oxide_vm/src/vm_runtime.rs index 512c0461..590e5e03 100644 --- a/crates/oxide_vm/src/vm_runtime.rs +++ b/crates/oxide_vm/src/vm_runtime.rs @@ -870,6 +870,9 @@ impl Vm { } // 对该处理器作用域内被中断的 for-of 循环执行 IteratorClose。 self.close_for_of_above(handler.for_of_depth); + // 异常穿越释放:逆序释放该作用域内登记的资源(规范序 IteratorClose 先、 + // DisposeResources 后)。释放方法抛错时新错误替代在途异常继续向外展开。 + self.dispose_above(handler.dispose_depth); if let Some(finally_pc) = handler.finally_pc { if handler.finally_active { // 新异常在 finally 体内抛出:覆盖在途异常与完成,继续向外展开, @@ -899,6 +902,8 @@ impl Vm { } } self.close_for_of_above(0); + // 未捕获兜底:释放栈上全部资源(异常穿越全作用域)。 + self.dispose_above(0); while let Some(frame) = self.frames.pop() { self.cell_stack.pop(); self.restore_frame(frame); @@ -1003,6 +1008,7 @@ mod tests { remaining_finally: 1, for_of_count: 0, for_in_count: 0, + dispose_count: 0, }); vm.iters.for_of_iters.push(crate::vm_state::ForOfEntry { iterator: JsValue::float(8.5), @@ -1146,19 +1152,17 @@ mod tests { #[test] fn using_declaration_registers_dispose_stack() { - // using 声明经 DISPOSE_REGISTER 把资源值压入释放栈。早 return 跳过函数 - // 体 POP(穿越面,归后续任务),资源残留栈上;run 边界(下次 run 入口) - // 清栈,残留条目不得跨 run 可见。 + // using 声明经 DISPOSE_REGISTER 把资源值压入释放栈。早 return 经逃出释放 + // (dispose_top_n)逆序释放本作用域资源,函数返回后栈空;run 边界清栈防 + // 跨 run 残留。 let mut vm = Vm::new(); let module = Arc::new(compile("function f() { using x = { a: 1 }; return 42; } f();")); vm.run(&module).expect("run"); - assert_eq!(vm.dispose_stack.len(), 1, "早 return 跳过 POP,资源残留栈上"); - assert!(vm.dispose_stack[0].0.is_object(), "登记值应为资源对象"); - assert_eq!(vm.dispose_stack[0].1, 0, "using 声明释放提示应为同步"); + assert!(vm.dispose_stack.is_empty(), "早 return 经逃出释放,函数返回后栈空"); - // 二次 run 入口清栈后重新登记:残留条目不跨 run 存活。 + // 二次 run 入口清栈:残留条目不跨 run 存活。 vm.run(&module).expect("run2"); - assert_eq!(vm.dispose_stack.len(), 1, "二次 run 应恰好登记一条新条目"); + assert!(vm.dispose_stack.is_empty(), "二次 run 后栈仍空"); // 执行状态清空同样清释放栈。 vm.clear_execution_state(); diff --git a/crates/oxide_vm/src/vm_support/tests.rs b/crates/oxide_vm/src/vm_support/tests.rs index 704cf04e..e7faf437 100644 --- a/crates/oxide_vm/src/vm_support/tests.rs +++ b/crates/oxide_vm/src/vm_support/tests.rs @@ -841,6 +841,7 @@ fn reset_clears_runtime_state_like_rerun() { finally_active: false, frame_depth: 0, for_of_depth: 0, + dispose_depth: 0, }); vm.exception_value = Some(JsValue::int(2)); vm.pending_exception = Some(JsValue::int(3)); diff --git a/crates/oxide_vm/tests/spill_stack_tests.rs b/crates/oxide_vm/tests/spill_stack_tests.rs index 1ed572df..d5110efb 100644 --- a/crates/oxide_vm/tests/spill_stack_tests.rs +++ b/crates/oxide_vm/tests/spill_stack_tests.rs @@ -64,7 +64,7 @@ fn spill_unspill_roundtrip() { #[test] fn frame_boundary_parent_spill_survives_call() { let child = int_module( - vec![Inst::load_const(Operand::Reg(3), 0), Inst::ret(Operand::Reg(3), 0, 0)], + vec![Inst::load_const(Operand::Reg(3), 0), Inst::ret(Operand::Reg(3), 0, 0, 0)], 99, 4, Vec::new(), @@ -89,7 +89,7 @@ fn nested_call_both_spill() { vec![ Inst::load_const(Operand::Reg(3), 0), Inst::inst_spill(Operand::Reg(3), 0), - Inst::ret(Operand::Reg(3), 0, 0), + Inst::ret(Operand::Reg(3), 0, 0, 0), ], 99, 4,