fix: for-of/for-in 提前退出时按规范顺序完成 IteratorClose

return/break/continue 逃出 for-of 时此前完全不执行 IteratorClose:
BREAK/CONTINUE/RETURN 指令现在经 ext 字携带词法逃出的迭代器层数
(for-of + for-in 各 16 位),运行时在全部 finally 穿越之后、真正
跳转/返回之前按 LIFO 关闭——与规范 §13.7.5.4(循环体完成值已含
内部 try-finally 处理,IteratorClose 在后)顺序一致。

- emit 词法:LabelCtx 增加 for-of/for-in 深度计数,loop/switch/label
  打开时快照,break/continue/return 按当前深度减目标深度计算逃出层数
  (switch 内 break 以 switch 打开点为基准,目标循环自身不计入)
- 指令编码:brk/cont/ret 三参构造,lower 跳转路径补发 ext 字
- 运行时:dispatch_break/continue/return 读取逃出计数,无 finally 时
  直接关闭后跳转/返回,有 finally 时随 Completion 悬挂由 TRY_FINALLY_END
  在穿越完成后消费;生成器 .return()/.throw() 注入经 complete_generator_return
  全关快照栈迭代器;return() 抛错经 unwind 替代完成值并关闭剩余迭代器
- 顺带:for-in 逃出残留(无 return() 语义,仅弹栈);for-await-of 的
  异步迭代器不做同步关闭(return() promise 须 await,走异步机制)
- 新增 15 项回归测试:return/labeled break/continue/switch/while/finally
  顺序/抛错优先/生成器注入/for-in/for-await-of 嵌套
This commit is contained in:
2026-08-16 09:31:39 +08:00
parent b885b672b9
commit a8378694be
23 changed files with 893 additions and 204 deletions
+53 -34
View File
@@ -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<IRFunction, String> {
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<String>, ctx: &mut CompileCtx,
&self, program: &oxide_parser::Program, module_path: &str, loader: &mut dyn ModuleSourceLoader,
path_stack: &mut Vec<String>, 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<String>,
&self, resolved: &ResolvedModule, loader: &mut dyn ModuleSourceLoader, path_stack: &mut Vec<String>,
) -> Result<IRFunction, String> {
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<u32, String> {
pub(crate) fn emit_module_call(&self, ctx: &mut CompileCtx, name: &str, arg_regs: &[u32]) -> Result<u32, String> {
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<Option<u32>, 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