From 06951dcec3523c39e131230c90c0d8b8953b0264 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=88=B1=E7=89=B9=E6=AF=8F=E7=BE=8A=E9=98=9F?= Date: Mon, 10 Aug 2026 10:18:39 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=BE=AA=E7=8E=AF=20let/const=20?= =?UTF-8?q?=E6=AF=8F=E8=BF=AD=E4=BB=A3=E7=BB=91=E5=AE=9A=EF=BC=88MAKE=5FCE?= =?UTF-8?q?LL=5FFRESH=20+=20=E8=A7=A3=E6=9E=84=E6=8D=95=E8=8E=B7=E4=BF=AE?= =?UTF-8?q?=E5=A4=8D=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- crates/oxide_bytecode/src/opcode.rs | 3 + crates/oxide_emit/src/closure.rs | 52 ++++++---- crates/oxide_emit/src/emit.rs | 7 +- crates/oxide_emit/src/expr/assignment.rs | 8 ++ crates/oxide_emit/src/expr/identifier.rs | 13 +++ crates/oxide_emit/src/expr/operator/mod.rs | 15 +++ crates/oxide_emit/src/shared/binding.rs | 32 +++--- crates/oxide_emit/src/stmt/declaration/var.rs | 2 +- .../src/stmt/exception/try_catch.rs | 2 +- crates/oxide_emit/src/stmt/iteration/for_.rs | 72 +++++++++++++- .../oxide_emit/src/stmt/iteration/for_in.rs | 29 ++++-- .../oxide_emit/src/stmt/iteration/for_of.rs | 7 +- crates/oxide_ir/src/contract.rs | 3 +- crates/oxide_regalloc/src/rewrite.rs | 2 +- crates/oxide_vm/src/dispatch/call.rs | 18 ++++ crates/oxide_vm/src/vm.rs | 3 + .../tests/loop_closure_binding_tests.rs | 98 +++++++++++++++++++ 17 files changed, 320 insertions(+), 46 deletions(-) create mode 100644 crates/oxide_vm/tests/loop_closure_binding_tests.rs diff --git a/crates/oxide_bytecode/src/opcode.rs b/crates/oxide_bytecode/src/opcode.rs index f20ffe8..17c72ad 100644 --- a/crates/oxide_bytecode/src/opcode.rs +++ b/crates/oxide_bytecode/src/opcode.rs @@ -217,6 +217,9 @@ define_opcodes! { FOR_AWAIT_OF_NEXT = 0x7B => "FOR_AWAIT_OF_NEXT", FOR_AWAIT_OF_DONE = 0x7C => "FOR_AWAIT_OF_DONE", FOR_AWAIT_OF_CLOSE = 0x7D => "FOR_AWAIT_OF_CLOSE", + // 无条件新建 cell 并替换 cell_stack[cell_idx],值为 regs[rd]——循环每迭代绑定 + // 把当前值拷入新 cell,本迭代闭包捕获新 cell(旧闭包仍指向旧 cell)。 + MAKE_CELL_FRESH = 0x7E => "MAKE_CELL_FRESH", // ── 位运算 (0x80-0x8F) ── BIT_AND = 0x80 => "BIT_AND", diff --git a/crates/oxide_emit/src/closure.rs b/crates/oxide_emit/src/closure.rs index c8b4860..ab0a6e5 100644 --- a/crates/oxide_emit/src/closure.rs +++ b/crates/oxide_emit/src/closure.rs @@ -28,13 +28,39 @@ impl Emitter { names } + /// 递归收集 binding pattern 内全部绑定标识符名(解构 `[a, b]` / `{x: y}` 嵌套)。 + fn collect_binding_pattern_names(&self, pattern: &oxide_parser::BindingPattern, out: &mut HashSet) { + match pattern { + oxide_parser::BindingPattern::BindingIdentifier(bi) => { + out.insert(bi.name.to_string()); + } + oxide_parser::BindingPattern::ArrayPattern(ap) => { + for p in ap.elements.iter().flatten() { + self.collect_binding_pattern_names(p, out); + } + if let Some(rest) = &ap.rest { + self.collect_binding_pattern_names(&rest.argument, out); + } + } + oxide_parser::BindingPattern::ObjectPattern(op) => { + for prop in &op.properties { + self.collect_binding_pattern_names(&prop.value, out); + } + if let Some(rest) = &op.rest { + self.collect_binding_pattern_names(&rest.argument, out); + } + } + oxide_parser::BindingPattern::AssignmentPattern(ap) => { + self.collect_binding_pattern_names(&ap.left, out); + } + } + } + /// 收集 for-in/for-of 头部声明(`var x` / 解构 pattern)绑定的名字。 fn collect_for_left_decl_names(&self, left: &oxide_parser::ForStatementLeft, out: &mut HashSet) { if let oxide_parser::ForStatementLeft::VariableDeclaration(vd) = left { for d in &vd.declarations { - if let oxide_parser::BindingPattern::BindingIdentifier(bi) = &d.id { - out.insert(bi.name.to_string()); - } + self.collect_binding_pattern_names(&d.id, out); } } } @@ -54,9 +80,7 @@ impl Emitter { match stmt { Statement::VariableDeclaration(vd) => { for d in &vd.declarations { - if let oxide_parser::BindingPattern::BindingIdentifier(bi) = &d.id { - out.insert(bi.name.to_string()); - } + self.collect_binding_pattern_names(&d.id, out); } } Statement::FunctionDeclaration(fd) => { @@ -86,22 +110,14 @@ impl Emitter { // for-in / for-of 头部的 var 声明是本函数局部绑定(遮蔽外层同名), // 须计入 own_bindings,否则闭包捕获分析会误判为捕获外层变量。 Statement::ForInStatement(fi) => { - if let oxide_parser::ForStatementLeft::VariableDeclaration(vd) = &fi.left { - for d in &vd.declarations { - if let oxide_parser::BindingPattern::BindingIdentifier(bi) = &d.id { - out.insert(bi.name.to_string()); - } - } + if let oxide_parser::ForStatementLeft::VariableDeclaration(_) = &fi.left { + self.collect_for_left_decl_names(&fi.left, out); } self.collect_decl_names_stmt(std::slice::from_ref(&fi.body), out); } Statement::ForOfStatement(fo) => { - if let oxide_parser::ForStatementLeft::VariableDeclaration(vd) = &fo.left { - for d in &vd.declarations { - if let oxide_parser::BindingPattern::BindingIdentifier(bi) = &d.id { - out.insert(bi.name.to_string()); - } - } + if let oxide_parser::ForStatementLeft::VariableDeclaration(_) = &fo.left { + self.collect_for_left_decl_names(&fo.left, out); } self.collect_decl_names_stmt(std::slice::from_ref(&fo.body), out); } diff --git a/crates/oxide_emit/src/emit.rs b/crates/oxide_emit/src/emit.rs index 711f768..b6f39b5 100644 --- a/crates/oxide_emit/src/emit.rs +++ b/crates/oxide_emit/src/emit.rs @@ -194,6 +194,10 @@ pub struct CompileCtx { /// try_stack 组成。每项标记是否为纯 catch handler(TRY_BEGIN,由 TRY_END /// 弹出):return 逃出 try 域时据此弹出栈顶连续纯 catch,防 handler 泄漏。 pub(crate) open_try_handlers: Vec, + /// 循环 update 段中应走寄存器(而非 cell)的被捕获绑定名:C 风格 for 的 + /// let/const 循环变量每迭代 fresh cell,update 写寄存器(不污染本迭代闭包 + /// 捕获的 cell),下一迭代 fresh 从寄存器拷入新 cell。 + pub(crate) register_update_names: Vec, } /// 函数体编译上下文:决定 `this`/`super` 绑定与参数前导(prologue)形态。 @@ -289,6 +293,7 @@ impl CompileCtx { const_overflow: false, with_stack: Vec::new(), open_try_handlers: Vec::new(), + register_update_names: Vec::new(), } } @@ -1117,7 +1122,7 @@ impl Emitter { } else { src_reg }; - self.emit_binding_pattern(pattern, src_reg, VariableDeclarationKind::Var, false, ctx)?; + self.emit_binding_pattern(pattern, src_reg, VariableDeclarationKind::Var, false, false, ctx)?; } ParamSpec::Identifier { name, initializer } => { if let Some(init) = initializer { diff --git a/crates/oxide_emit/src/expr/assignment.rs b/crates/oxide_emit/src/expr/assignment.rs index c08dd30..d666270 100644 --- a/crates/oxide_emit/src/expr/assignment.rs +++ b/crates/oxide_emit/src/expr/assignment.rs @@ -18,6 +18,14 @@ impl Emitter { fn emit_compound_identifier_static( &self, name: &str, op: OpCode, rhs: u32, ctx: &mut CompileCtx, ) -> Result { + // 循环 update 段:被捕获绑定走寄存器 RMW(C 风格 for 每迭代 fresh, + // update 写寄存器供下一迭代 fresh 拷贝,不污染本迭代闭包捕获的 cell)。 + if ctx.register_update_names.iter().any(|n| n == name) { + if let Some(reg) = ctx.scopes.symbols.lookup_any(name) { + ctx.inst(Inst::new(op, Operand::Reg(reg), Operand::Reg(rhs), Operand::None)); + return Ok(reg); + } + } let uv_idx = ctx.current_upvalue_captures.iter().position(|u| u.name == name); let captured_cell = ctx.captured_bindings.get(name).copied(); if let Some(uv) = uv_idx { diff --git a/crates/oxide_emit/src/expr/identifier.rs b/crates/oxide_emit/src/expr/identifier.rs index fbe4220..7a1d9af 100644 --- a/crates/oxide_emit/src/expr/identifier.rs +++ b/crates/oxide_emit/src/expr/identifier.rs @@ -112,6 +112,19 @@ impl Emitter { /// - `const_flag` 为 1 时走 STORE_VAR 运行时 const guard(对 const 再赋值报错)。 /// - upvalue 与 cell 写穿共享单元,不设 guard。 pub(crate) fn emit_identifier_store(&self, name: &str, val_reg: u32, const_flag: u16, ctx: &mut CompileCtx) { + // 循环 update 段:被捕获绑定走寄存器而非 cell(C 风格 for 每迭代 fresh, + // update 写寄存器供下一迭代 fresh 拷贝,不污染本迭代闭包捕获的 cell)。 + if ctx.register_update_names.iter().any(|n| n == name) { + if let Some(reg) = ctx.scopes.symbols.lookup_any(name) { + ctx.inst(Inst::new( + OpCode::STORE_VAR, + Operand::Reg(reg), + Operand::Reg(val_reg), + Operand::Imm(const_flag), + )); + return; + } + } // 目标若是 upvalue 引用,走 STORE_UPVALUE if let Some(uv_idx) = ctx.current_upvalue_captures.iter().position(|u| u.name == name) { ctx.inst(Inst::new( diff --git a/crates/oxide_emit/src/expr/operator/mod.rs b/crates/oxide_emit/src/expr/operator/mod.rs index 83e7a42..f76fdbd 100644 --- a/crates/oxide_emit/src/expr/operator/mod.rs +++ b/crates/oxide_emit/src/expr/operator/mod.rs @@ -418,6 +418,21 @@ impl Emitter { ) -> Result { let uv_idx = ctx.current_upvalue_captures.iter().position(|u| u.name == name); let captured_cell = ctx.captured_bindings.get(name).copied(); + // 循环 update 段:被捕获绑定走寄存器 INC/DEC(C 风格 for 每迭代 fresh, + // update 写寄存器供下一迭代 fresh 拷贝,不污染本迭代闭包捕获的 cell)。 + if ctx.register_update_names.iter().any(|n| n == name) { + if let Some(reg) = ctx.scopes.symbols.lookup_any(name) { + let result_reg = ctx.alloc_reg(); + let op = match (update.operator, update.prefix) { + (UpdateOperator::Increment, true) => OpCode::INC_PRE, + (UpdateOperator::Increment, false) => OpCode::INC_POST, + (UpdateOperator::Decrement, true) => OpCode::DEC_PRE, + (UpdateOperator::Decrement, false) => OpCode::DEC_POST, + }; + ctx.inst(Inst::new(op, Operand::Reg(reg), Operand::Reg(result_reg), Operand::Reg(result_reg))); + return Ok(result_reg); + } + } if let Some(uv) = uv_idx { // upvalue:LOAD_UPVALUE + 常量 1 + ADD/SUB + STORE_UPVALUE; // 后缀形式返回旧值(前缀返回新值)。 diff --git a/crates/oxide_emit/src/shared/binding.rs b/crates/oxide_emit/src/shared/binding.rs index 384ac8d..b11258f 100644 --- a/crates/oxide_emit/src/shared/binding.rs +++ b/crates/oxide_emit/src/shared/binding.rs @@ -48,8 +48,11 @@ impl Emitter { Ok(val_reg) } + /// 绑定单个标识符到槽位。`fresh_cell` 为 true 时对被捕获绑定用 MAKE_CELL_FRESH + /// (循环每迭代绑定:无条件新建 cell,本迭代闭包捕获新 cell)。 fn emit_bind_target( - &self, name: &str, src_reg: u32, kind: VariableDeclarationKind, is_const: bool, ctx: &mut CompileCtx, + &self, name: &str, src_reg: u32, kind: VariableDeclarationKind, is_const: bool, fresh_cell: bool, + ctx: &mut CompileCtx, ) -> Result<(), String> { let target_reg = if matches!(kind, VariableDeclarationKind::Var) { // `var` 名已预声明(hoisting),复用预登记槽位而非新分配,使 n_registers 与 let/const 一致。 @@ -66,8 +69,9 @@ impl Emitter { var_reg }; if let Some(&cell_idx) = ctx.captured_bindings.get(name) { + let op = if fresh_cell { OpCode::MAKE_CELL_FRESH } else { OpCode::MAKE_CELL }; ctx.inst(Inst::new( - OpCode::MAKE_CELL, + op, Operand::Reg(src_reg), Operand::Imm(cell_idx as u16), Operand::None, @@ -119,14 +123,14 @@ impl Emitter { pub(crate) fn emit_binding_pattern( &self, pattern: &BindingPattern, src_reg: u32, kind: VariableDeclarationKind, is_const: bool, - ctx: &mut CompileCtx, + fresh_cell: bool, ctx: &mut CompileCtx, ) -> Result<(), String> { match pattern { BindingPattern::BindingIdentifier(bi) => { - self.emit_bind_target(bi.name.as_str(), src_reg, kind, is_const, ctx) + self.emit_bind_target(bi.name.as_str(), src_reg, kind, is_const, fresh_cell, ctx) } - BindingPattern::ArrayPattern(ap) => self.emit_array_binding(ap, src_reg, kind, is_const, ctx), - BindingPattern::ObjectPattern(op) => self.emit_object_binding(op, src_reg, kind, is_const, ctx), + BindingPattern::ArrayPattern(ap) => self.emit_array_binding(ap, src_reg, kind, is_const, fresh_cell, ctx), + BindingPattern::ObjectPattern(op) => self.emit_object_binding(op, src_reg, kind, is_const, fresh_cell, ctx), BindingPattern::AssignmentPattern(ap) => { // `[x = fn]`:默认值函数按绑定名推断 name。 let name = match &ap.left { @@ -134,13 +138,14 @@ impl Emitter { _ => None, }; let val_reg = self.emit_default_if_undefined(src_reg, &ap.right, name, ctx)?; - self.emit_binding_pattern(&ap.left, val_reg, kind, is_const, ctx) + self.emit_binding_pattern(&ap.left, val_reg, kind, is_const, fresh_cell, ctx) } } } fn emit_array_binding( - &self, ap: &ArrayPattern, src_reg: u32, kind: VariableDeclarationKind, is_const: bool, ctx: &mut CompileCtx, + &self, ap: &ArrayPattern, src_reg: u32, kind: VariableDeclarationKind, is_const: bool, fresh_cell: bool, + ctx: &mut CompileCtx, ) -> Result<(), String> { ctx.inst(Inst::new(OpCode::FOR_OF_INIT, Operand::None, Operand::Reg(src_reg), Operand::None)); for elem in &ap.elements { @@ -149,12 +154,12 @@ impl Emitter { let val_reg = ctx.alloc_reg(); ctx.inst(Inst::new(OpCode::FOR_OF_NEXT, Operand::Reg(val_reg), Operand::None, Operand::None)); if let Some(pattern) = elem { - self.emit_binding_pattern(pattern, val_reg, kind, is_const, ctx)?; + self.emit_binding_pattern(pattern, val_reg, kind, is_const, fresh_cell, ctx)?; } } if let Some(rest) = &ap.rest { let rest_reg = self.emit_collect_rest_array(ctx)?; - self.emit_binding_pattern(&rest.argument, rest_reg, kind, is_const, ctx)?; + self.emit_binding_pattern(&rest.argument, rest_reg, kind, is_const, fresh_cell, ctx)?; } ctx.inst(Inst::new(OpCode::FOR_OF_CLOSE, Operand::None, Operand::None, Operand::None)); Ok(()) @@ -193,7 +198,8 @@ impl Emitter { } fn emit_object_binding( - &self, op: &ObjectPattern, src_reg: u32, kind: VariableDeclarationKind, is_const: bool, ctx: &mut CompileCtx, + &self, op: &ObjectPattern, src_reg: u32, kind: VariableDeclarationKind, is_const: bool, fresh_cell: bool, + ctx: &mut CompileCtx, ) -> Result<(), String> { // 对象解构先 ToObject(rhs):null/undefined 抛 TypeError(含空 pattern), // 原始值包装为对应对象,保证属性读取与 rest 都以对象为源。 @@ -221,7 +227,7 @@ impl Emitter { self.emit_push_excluded_key(arr, Some(key_reg), "", excl_push, ctx); excl_push += 1; } - self.emit_binding_pattern(&prop.value, prop_reg, kind, is_const, ctx)?; + self.emit_binding_pattern(&prop.value, prop_reg, kind, is_const, fresh_cell, ctx)?; } if let Some(rest) = &op.rest { let rest_reg = ctx.alloc_reg(); @@ -232,7 +238,7 @@ impl Emitter { excluded_idx as u32, excl_arr_reg.map(Operand::Reg), )); - self.emit_binding_pattern(&rest.argument, rest_reg, kind, is_const, ctx)?; + self.emit_binding_pattern(&rest.argument, rest_reg, kind, is_const, fresh_cell, ctx)?; } Ok(()) } diff --git a/crates/oxide_emit/src/stmt/declaration/var.rs b/crates/oxide_emit/src/stmt/declaration/var.rs index ffdb6bf..4744048 100644 --- a/crates/oxide_emit/src/stmt/declaration/var.rs +++ b/crates/oxide_emit/src/stmt/declaration/var.rs @@ -22,7 +22,7 @@ impl Emitter { } if let Some(init) = &d.init { let val_reg = self.emit_expression(init, ctx)?; - self.emit_binding_pattern(&d.id, val_reg, decl.kind, is_const, ctx)?; + self.emit_binding_pattern(&d.id, val_reg, decl.kind, is_const, false, ctx)?; if let BindingPattern::BindingIdentifier(bi) = &d.id { if crate::is_anonymous_function_definition(init) { if let Some(sub_mod) = ctx.nested.last_mut() { diff --git a/crates/oxide_emit/src/stmt/exception/try_catch.rs b/crates/oxide_emit/src/stmt/exception/try_catch.rs index 215aa7a..840a757 100644 --- a/crates/oxide_emit/src/stmt/exception/try_catch.rs +++ b/crates/oxide_emit/src/stmt/exception/try_catch.rs @@ -56,7 +56,7 @@ impl Emitter { let src_reg = ctx.alloc_reg(); // 异常值在 VM 物理 regs[0](unwind 展开处写入),STORE_VAR a=None 读回。 ctx.inst(Inst::new(OpCode::STORE_VAR, Operand::Reg(src_reg), Operand::None, Operand::None)); - self.emit_binding_pattern(¶m.pattern, src_reg, VariableDeclarationKind::Let, false, ctx)?; + self.emit_binding_pattern(¶m.pattern, src_reg, VariableDeclarationKind::Let, false, false, ctx)?; } let mut last_catch_result: Option = None; for s in &catch.body.body { diff --git a/crates/oxide_emit/src/stmt/iteration/for_.rs b/crates/oxide_emit/src/stmt/iteration/for_.rs index 5e1144a..1508718 100644 --- a/crates/oxide_emit/src/stmt/iteration/for_.rs +++ b/crates/oxide_emit/src/stmt/iteration/for_.rs @@ -8,6 +8,30 @@ use oxide_ir::operand::Operand; use oxide_parser::{BindingPattern, ForStatementInit, Statement, VariableDeclarationKind}; impl Emitter { + /// 递归收集解构 pattern 内全部绑定标识符名(用于循环头声明被捕获时的 fresh 判定)。 + fn collect_pattern_binding_names(&self, pattern: &BindingPattern, out: &mut Vec) { + match pattern { + BindingPattern::BindingIdentifier(bi) => out.push(bi.name.as_str().to_string()), + BindingPattern::ArrayPattern(ap) => { + for p in ap.elements.iter().flatten() { + self.collect_pattern_binding_names(p, out); + } + if let Some(rest) = &ap.rest { + self.collect_pattern_binding_names(&rest.argument, out); + } + } + BindingPattern::ObjectPattern(op) => { + for prop in &op.properties { + self.collect_pattern_binding_names(&prop.value, out); + } + if let Some(rest) = &op.rest { + self.collect_pattern_binding_names(&rest.argument, out); + } + } + BindingPattern::AssignmentPattern(ap) => self.collect_pattern_binding_names(&ap.left, out), + } + } + pub(crate) fn emit_for_statement(&self, stmt: &Statement, ctx: &mut CompileCtx) -> Result, String> { let Statement::ForStatement(fr) = stmt else { return Ok(None); @@ -17,6 +41,8 @@ impl Emitter { let end_label = ctx.next_label_id(); ctx.push_loop(end_label, update_label); 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(); if let Some(init) = &fr.init { if let Some(expr) = init.as_expression() { self.emit_expression(expr, ctx)?; @@ -25,7 +51,23 @@ impl Emitter { let is_const = matches!(decl.kind, VariableDeclarationKind::Const); if let Some(init_expr) = &d.init { let val_reg = self.emit_expression(init_expr, ctx)?; - self.emit_binding_pattern(&d.id, val_reg, decl.kind, is_const, ctx)?; + self.emit_binding_pattern(&d.id, val_reg, decl.kind, is_const, false, ctx)?; + // 被捕获绑定:fresh 从寄存器读当前值,须把 init 值写入寄存器槽。 + // emit_binding_pattern 对捕获绑定只建 cell,不写寄存器。 + let mut names = Vec::new(); + self.collect_pattern_binding_names(&d.id, &mut names); + for name in &names { + if ctx.captured_bindings.contains_key(name) { + if let Some(reg) = ctx.scopes.symbols.lookup_any(name) { + ctx.inst(Inst::new( + OpCode::STORE_VAR, + Operand::Reg(reg), + Operand::Reg(val_reg), + Operand::None, + )); + } + } + } } else if let BindingPattern::BindingIdentifier(bi) = &d.id { let idx = ctx.add_constant(Constant::Undefined); let tmp = ctx.alloc_reg(); @@ -48,10 +90,32 @@ impl Emitter { )); ctx.init_var(bi.name.as_str()); } + // 记录被捕获的 let/const 声明(解构 pattern 递归收集绑定名)。 + if !matches!(decl.kind, VariableDeclarationKind::Var) { + let mut names = Vec::new(); + self.collect_pattern_binding_names(&d.id, &mut names); + for name in &names { + if let Some(&cell_idx) = ctx.captured_bindings.get(name) { + fresh_bindings.push((name.clone(), cell_idx)); + } + } + } } } } ctx.labels.set_label_pos(start_label, ctx.insts.len()); + // 每迭代 fresh cell:被捕获的 let/const 循环变量在迭代开始拷贝当前值到新 cell, + // 本迭代闭包捕获新 cell(规范 CreatePerIterationEnvironment)。 + // 值源是循环变量的寄存器(update 段写寄存器而非 cell),首迭代即 init 值。 + for (name, cell_idx) in &fresh_bindings { + let reg = ctx.scopes.symbols.lookup_any(name).expect("declared loop binding"); + ctx.inst(Inst::new( + OpCode::MAKE_CELL_FRESH, + Operand::Reg(reg), + Operand::Imm(*cell_idx as u16), + Operand::None, + )); + } if let Some(test) = &fr.test { let test_reg = self.emit_expression(test, ctx)?; ctx.inst(Inst::jmp_if_false(test_reg, end_label)); @@ -59,7 +123,13 @@ impl Emitter { self.emit_statement(&fr.body, ctx)?; ctx.labels.set_label_pos(update_label, ctx.insts.len()); if let Some(update) = &fr.update { + // 被捕获的 let/const 循环变量在 update 段写寄存器(而非 cell), + // 供下一迭代 fresh 拷贝——否则 CELL_SET 会污染本迭代闭包捕获的 cell。 + let prev = std::mem::take(&mut ctx.register_update_names); + let names: Vec = fresh_bindings.iter().map(|(n, _)| n.clone()).collect(); + ctx.register_update_names = names; self.emit_expression(update, ctx)?; + ctx.register_update_names = prev; } ctx.inst(Inst::jmp(start_label)); ctx.labels.set_label_pos(end_label, ctx.insts.len()); diff --git a/crates/oxide_emit/src/stmt/iteration/for_in.rs b/crates/oxide_emit/src/stmt/iteration/for_in.rs index 364e146..51a7749 100644 --- a/crates/oxide_emit/src/stmt/iteration/for_in.rs +++ b/crates/oxide_emit/src/stmt/iteration/for_in.rs @@ -29,15 +29,30 @@ impl Emitter { ctx.inst(Inst::new(OpCode::FOR_IN_NEXT, Operand::Reg(key_reg), Operand::None, Operand::None)); match &fi.left { ForStatementLeft::VariableDeclaration(decl) => { + // let/const 声明对被捕获绑定用 fresh cell(每迭代新 cell);var 单绑定。 + let fresh_cell = !matches!(decl.kind, VariableDeclarationKind::Var); for d in &decl.declarations { - let name = match &d.id { - oxide_parser::BindingPattern::BindingIdentifier(bi) => bi.name.as_str(), + match &d.id { + oxide_parser::BindingPattern::BindingIdentifier(bi) => { + let is_const = matches!(decl.kind, VariableDeclarationKind::Const); + let name = bi.name.as_str(); + let var_reg = ctx.alloc_reg(); + ctx.declare(name, var_reg, decl.kind, is_const)?; + if let Some(&cell_idx) = ctx.captured_bindings.get(name) { + let op = if fresh_cell { OpCode::MAKE_CELL_FRESH } else { OpCode::MAKE_CELL }; + ctx.inst(Inst::new(op, Operand::Reg(key_reg), Operand::Imm(cell_idx as u16), Operand::None)); + } else { + ctx.inst(Inst::new( + OpCode::STORE_VAR, + Operand::Reg(var_reg), + Operand::Reg(key_reg), + Operand::None, + )); + } + ctx.init_var(name); + } _ => return Err("destructuring not supported".into()), - }; - let var_reg = ctx.alloc_reg(); - ctx.declare(name, var_reg, decl.kind, matches!(decl.kind, VariableDeclarationKind::Const))?; - ctx.inst(Inst::new(OpCode::STORE_VAR, Operand::Reg(var_reg), Operand::Reg(key_reg), Operand::None)); - ctx.init_var(name); + } } } ForStatementLeft::AssignmentTargetIdentifier(id_ref) => { diff --git a/crates/oxide_emit/src/stmt/iteration/for_of.rs b/crates/oxide_emit/src/stmt/iteration/for_of.rs index 90faa1e..1801dbd 100644 --- a/crates/oxide_emit/src/stmt/iteration/for_of.rs +++ b/crates/oxide_emit/src/stmt/iteration/for_of.rs @@ -9,7 +9,7 @@ use crate::{CompileCtx, Emitter}; use oxide_bytecode::opcode::OpCode; use oxide_ir::inst::Inst; use oxide_ir::operand::Operand; -use oxide_parser::{ForOfStatement, ForStatementLeft, Statement}; +use oxide_parser::{ForOfStatement, ForStatementLeft, Statement, VariableDeclarationKind}; impl Emitter { pub(crate) fn emit_for_of_statement(&self, stmt: &Statement, ctx: &mut CompileCtx) -> Result, String> { @@ -84,13 +84,16 @@ impl Emitter { } /// for-of/for-await-of 左侧绑定:把当前迭代值 `val_reg` 写入声明/赋值目标。 + /// let/const 声明对被捕获绑定用 fresh cell(每迭代新 cell,规范 per-iteration 绑定); + /// var 保持单绑定。 fn emit_for_of_left_assignment( &self, left: &ForStatementLeft, val_reg: u32, ctx: &mut CompileCtx, ) -> Result<(), String> { match left { ForStatementLeft::VariableDeclaration(decl) => { + let fresh_cell = !matches!(decl.kind, VariableDeclarationKind::Var); for d in &decl.declarations { - self.emit_binding_pattern(&d.id, val_reg, decl.kind, false, ctx)?; + self.emit_binding_pattern(&d.id, val_reg, decl.kind, false, fresh_cell, ctx)?; } } ForStatementLeft::AssignmentTargetIdentifier(id_ref) => { diff --git a/crates/oxide_ir/src/contract.rs b/crates/oxide_ir/src/contract.rs index 2b77a76..ae71912 100644 --- a/crates/oxide_ir/src/contract.rs +++ b/crates/oxide_ir/src/contract.rs @@ -54,6 +54,7 @@ impl Inst { | OpCode::TRY_FINALLY_BEGIN | OpCode::TRY_FINALLY_END | OpCode::MAKE_CELL + | OpCode::MAKE_CELL_FRESH | OpCode::CELL_SET | OpCode::STORE_UPVALUE | OpCode::SET_PROP @@ -254,7 +255,7 @@ impl Inst { OpCode::SPILL => push_operand(&mut uses, &self.rd), OpCode::UNSPILL => {} // MAKE_CELL:cell 初值读 rd;CELL_SET/STORE_UPVALUE 读 a - OpCode::MAKE_CELL => push_operand(&mut uses, &self.rd), + OpCode::MAKE_CELL | OpCode::MAKE_CELL_FRESH => push_operand(&mut uses, &self.rd), OpCode::CELL_SET | OpCode::STORE_UPVALUE => push_operand(&mut uses, &self.a), // 写对象属性:rd/a/b 全 use OpCode::SET_PROP diff --git a/crates/oxide_regalloc/src/rewrite.rs b/crates/oxide_regalloc/src/rewrite.rs index 78d218a..7b325bc 100644 --- a/crates/oxide_regalloc/src/rewrite.rs +++ b/crates/oxide_regalloc/src/rewrite.rs @@ -264,7 +264,7 @@ fn rewrite_inst( inst.op ); } - OpCode::MAKE_CELL | OpCode::CELL_GET | OpCode::CELL_SET | OpCode::STORE_UPVALUE => { + OpCode::MAKE_CELL | OpCode::MAKE_CELL_FRESH | OpCode::CELL_GET | OpCode::CELL_SET | OpCode::STORE_UPVALUE => { debug_assert!( matches!(b, Operand::Imm(_) | Operand::None), "{}: b 槽是 imm16 编码位,不得为 Reg", diff --git a/crates/oxide_vm/src/dispatch/call.rs b/crates/oxide_vm/src/dispatch/call.rs index 5122f2e..47a18e6 100644 --- a/crates/oxide_vm/src/dispatch/call.rs +++ b/crates/oxide_vm/src/dispatch/call.rs @@ -204,6 +204,24 @@ impl Vm { Ok(()) } + /// 无条件新建 cell 并替换 cell_stack[cell_idx],值为 `regs[rd]`。 + /// + /// 循环每迭代绑定用:把当前迭代值拷入新 cell,本迭代创建的闭包经 + /// CREATE_CLOSURE 捕获新 cell(旧闭包仍指向旧 cell,值保持)。与 + /// `dispatch_make_cell` 的唯一区别是恒新建、不复用占位 cell。 + #[allow(dead_code)] + pub(crate) fn dispatch_make_cell_fresh(&mut self, rd: usize, instr: u32) -> Result<(), String> { + let cell_idx = opcode::imm16(instr) as usize; + let value = self.regs[rd]; + let current = self.cell_stack.last_mut().unwrap(); + while current.len() <= cell_idx { + current.push(std::ptr::null_mut()); + } + let cell = self.gc_state.session_epoch.alloc(Cell::new(value, true)); + current[cell_idx] = cell as *mut Cell; + Ok(()) + } + #[allow(dead_code)] pub(crate) fn dispatch_cell_get(&mut self, rd: usize, a: usize, b: usize) -> Result<(), String> { let cell_idx = b; diff --git a/crates/oxide_vm/src/vm.rs b/crates/oxide_vm/src/vm.rs index b90b4c4..5fd6a57 100644 --- a/crates/oxide_vm/src/vm.rs +++ b/crates/oxide_vm/src/vm.rs @@ -1101,6 +1101,9 @@ impl Vm { OpCode::MAKE_CELL => { self.dispatch_make_cell(rd, instr)?; } + OpCode::MAKE_CELL_FRESH => { + self.dispatch_make_cell_fresh(rd, instr)?; + } OpCode::CELL_GET => { self.dispatch_cell_get(rd, a, b)?; } diff --git a/crates/oxide_vm/tests/loop_closure_binding_tests.rs b/crates/oxide_vm/tests/loop_closure_binding_tests.rs new file mode 100644 index 0000000..9a69e75 --- /dev/null +++ b/crates/oxide_vm/tests/loop_closure_binding_tests.rs @@ -0,0 +1,98 @@ +//! 循环每迭代绑定(CreatePerIterationEnvironment):for/for-of/for-in 的 let/const +//! 闭包捕获各自迭代快照。对应 PLAN 06-01(正确性报告 P1/P2)。 + +use oxide_compiler::compiler::Compiler; +use oxide_types::value::JsValue; +use oxide_vm::vm::Vm; + +fn eval(source: &str) -> Result<(Vm, JsValue), String> { + let allocator = oxide_parser::Allocator::default(); + let program = oxide_parser::parse(&allocator, source).map_err(|e| format!("Parse error: {:?}", e))?; + let module = Compiler::new().compile(&program).map_err(|e| format!("Compile error: {}", e))?; + let mut vm = Vm::new(); + let result = vm.run(&module)?; + Ok((vm, result)) +} + +fn to_str(vm: &Vm, val: JsValue) -> String { + vm.lookup_str(val).unwrap_or_default() +} + +#[test] +fn c_style_for_let_captures_per_iteration() { + let (vm, result) = + eval("let fns = []; for (let i = 0; i < 3; i++) fns.push(() => i); [fns[0](), fns[1](), fns[2]()].join(',')") + .unwrap(); + assert_eq!(to_str(&vm, result), "0,1,2"); +} + +#[test] +fn c_style_for_let_captures_after_multiple_updates() { + let (vm, result) = eval("let fns = []; for (let i = 0; i < 5; i += 2) fns.push(() => i); [fns[0](), fns[1](), fns[2]()].join(',')") + .unwrap(); + assert_eq!(to_str(&vm, result), "0,2,4"); +} + +#[test] +fn c_style_for_var_remains_single_binding() { + // var 循环变量是单绑定(规范:闭包捕获同一绑定,最终值共享)。 + let (vm, result) = + eval("let fns = []; for (var i = 0; i < 3; i++) fns.push(() => i); [fns[0](), fns[1](), fns[2]()].join(',')") + .unwrap(); + assert_eq!(to_str(&vm, result), "3,3,3"); +} + +#[test] +fn for_of_const_captures_per_iteration() { + let (vm, result) = eval("let fns = []; for (const a of [1, 2]) fns.push(() => a); [fns[0](), fns[1]()].join(',')").unwrap(); + assert_eq!(to_str(&vm, result), "1,2"); +} + +#[test] +fn for_of_let_captures_per_iteration() { + let (vm, result) = + eval("let fns = []; for (let a of [10, 20, 30]) fns.push(() => a); [fns[0](), fns[1](), fns[2]()].join(',')").unwrap(); + assert_eq!(to_str(&vm, result), "10,20,30"); +} + +#[test] +fn for_of_destructuring_captures_per_iteration() { + // P2:解构 + 闭包捕获不再产出垃圾值。 + let (vm, result) = + eval("let fns = []; for (const [a, b] of [[1, 2], [3, 4]]) fns.push(() => a + b); [fns[0](), fns[1]()].join(',')") + .unwrap(); + assert_eq!(to_str(&vm, result), "3,7"); +} + +#[test] +fn for_of_object_destructuring_captures_per_iteration() { + let (vm, result) = + eval("let fns = []; for (const {x, y} of [{x:1,y:2},{x:3,y:4}]) fns.push(() => x * y); [fns[0](), fns[1]()].join(',')") + .unwrap(); + assert_eq!(to_str(&vm, result), "2,12"); +} + +#[test] +fn for_in_const_captures_per_iteration() { + let (vm, result) = eval("let fns = []; for (const k in {a:1,b:2}) fns.push(() => k); [fns[0](), fns[1]()].join(',')").unwrap(); + assert_eq!(to_str(&vm, result), "a,b"); +} + +#[test] +fn nested_loop_closure_captures_distinct_bindings() { + let (vm, result) = eval( + "let fns = []; for (let i = 0; i < 2; i++) for (let j = 0; j < 2; j++) fns.push(() => i + j); [fns[0](), fns[1](), fns[2](), fns[3]()].join(',')", + ) + .unwrap(); + assert_eq!(to_str(&vm, result), "0,1,1,2"); +} + +#[test] +fn loop_variable_mutation_visible_to_same_iteration() { + // 同迭代内变量修改(非闭包)仍直接可见:fresh 每迭代一次,迭代内读写走同一 cell。 + let (vm, result) = eval( + "let fns = []; for (let i = 0; i < 3; i++) { fns.push(() => i); i += 0; } [fns[0](), fns[1](), fns[2]()].join(',')", + ) + .unwrap(); + assert_eq!(to_str(&vm, result), "0,1,2"); +}