From b611e26f1142913603b09614b3fb747ed5c01a7f 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: Wed, 5 Aug 2026 19:19:29 +0800 Subject: [PATCH] =?UTF-8?q?fix(compiler):=20=E5=AE=8C=E5=96=84=E9=97=AD?= =?UTF-8?q?=E5=8C=85=E6=8D=95=E8=8E=B7=EF=BC=88cell=20=E7=B4=A2=E5=BC=95?= =?UTF-8?q?=E7=A8=B3=E5=AE=9A=E3=80=81=E5=8F=82=E6=95=B0=E9=81=AE=E8=94=BD?= =?UTF-8?q?=E3=80=81=E5=87=BD=E6=95=B0/=E7=B1=BB=E5=90=8D=E6=8D=95?= =?UTF-8?q?=E8=8E=B7=E3=80=81b=20=E6=A7=BD=E7=AB=8B=E5=8D=B3=E6=95=B0?= =?UTF-8?q?=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- crates/oxide_compiler/src/compiler.rs | 86 ++++++++++++------- crates/oxide_compiler/src/emit_ctx.rs | 1 - crates/oxide_compiler/src/expr/assignment.rs | 9 +- crates/oxide_compiler/src/expr/identifier.rs | 13 +-- .../oxide_compiler/src/expr/operator/mod.rs | 11 +-- crates/oxide_compiler/src/ir/lower.rs | 8 +- crates/oxide_compiler/src/shared/binding.rs | 4 +- .../src/stmt/declaration/class_decl.rs | 6 +- .../src/stmt/declaration/function_decl.rs | 6 +- .../src/stmt/declaration/var.rs | 5 +- crates/oxide_vm/tests/arrow_tests.rs | 8 ++ 11 files changed, 87 insertions(+), 70 deletions(-) diff --git a/crates/oxide_compiler/src/compiler.rs b/crates/oxide_compiler/src/compiler.rs index 1eb6323..010f398 100644 --- a/crates/oxide_compiler/src/compiler.rs +++ b/crates/oxide_compiler/src/compiler.rs @@ -1,4 +1,4 @@ -use std::collections::{HashMap, HashSet}; +use std::collections::{BTreeMap, HashMap, HashSet}; use crate::emit_ctx::{LabelCtx, ScopeCtx}; use crate::ir::inst::Inst; @@ -145,9 +145,10 @@ pub(crate) struct CompileCtx { pub(crate) current_upvalue_captures: Vec, /// 本函数作用域声明的绑定名(参数 + 变量/函数声明,AST 收集,emit 前确定)。 pub(crate) own_bindings: HashSet, - /// 本函数被嵌套函数捕获的绑定名(AST 分析,emit 前确定)。 - /// 捕获判断(MAKE_CELL / CELL_GET / CELL_SET)统一查此集合,消除符号表时序依赖。 - pub(crate) captured_bindings: HashSet, + /// 本函数被嵌套函数捕获的绑定名 → cell_idx(名字排序分配,稳定跨 run)。 + /// 捕获判断(MAKE_CELL / CELL_GET / CELL_SET)与子函数 upvalue cell_idx 统一查此映射, + /// 消除符号表时序依赖与 cell 索引错位。 + pub(crate) captured_bindings: BTreeMap, /// Set when alloc_reg() overflows into the reserved this/new.target range (≥254). /// Checked after each emit phase to produce a compile error rather than silent corruption. pub(crate) reg_overflow: bool, @@ -210,7 +211,6 @@ impl CompileCtx { builtin_reg_map: Vec::new(), private_name_map: Vec::new(), next_private_name_id: 1, - cell_registry: Vec::new(), }, nested: Vec::new(), enclosing_this_reg: 254, // conventional this register at top level @@ -221,7 +221,7 @@ impl CompileCtx { field_buffer: None, current_upvalue_captures: Vec::new(), own_bindings: HashSet::new(), - captured_bindings: HashSet::new(), + captured_bindings: BTreeMap::new(), reg_overflow: false, const_overflow: false, } @@ -522,6 +522,17 @@ impl Compiler { // ── 闭包捕获分析(AST 级,时序无关)── + /// 收集函数参数的绑定名(BindingIdentifier 形态)。 + fn collect_fn_param_names(&self, params: &oxide_parser::FormalParameters) -> HashSet { + let mut names = HashSet::new(); + for p in ¶ms.items { + if let oxide_parser::BindingPattern::BindingIdentifier(bi) = &p.pattern { + names.insert(bi.name.to_string()); + } + } + names + } + /// 收集当前函数作用域声明的绑定名(参数 + 变量/函数声明,含嵌套 block,不含嵌套函数体)。 fn collect_own_binding_names(&self, param_names: &[&str], stmts: &[Statement]) -> HashSet { let mut names = HashSet::new(); @@ -621,6 +632,7 @@ impl Compiler { Statement::FunctionDeclaration(fd) => { let body: &[Statement] = fd.body.as_ref().map(|b| &b.statements[..]).unwrap_or(&[]); let mut inner = shadow.clone(); + inner.extend(self.collect_fn_param_names(&fd.params)); inner.extend(self.collect_own_binding_names(&[], body)); self.collect_capture_names_shadowed(body, ref_set, &inner, out); } @@ -728,11 +740,13 @@ impl Compiler { Expression::FunctionExpression(fe) => { let body: &[Statement] = fe.body.as_ref().map(|b| &b.statements[..]).unwrap_or(&[]); let mut inner = shadow.clone(); + inner.extend(self.collect_fn_param_names(&fe.params)); inner.extend(self.collect_own_binding_names(&[], body)); self.collect_capture_names_shadowed(body, ref_set, &inner, out); } Expression::ArrowFunctionExpression(ae) => { let mut inner = shadow.clone(); + inner.extend(self.collect_fn_param_names(&ae.params)); inner.extend(self.collect_own_binding_names(&[], &ae.body.statements)); self.collect_capture_names_shadowed(&ae.body.statements, ref_set, &inner, out); } @@ -836,12 +850,19 @@ impl Compiler { } /// 分析本函数:哪些绑定被任意深度嵌套函数捕获 → captured_bindings。 - fn collect_captured_bindings(&self, stmts: &[Statement], own: &HashSet) -> HashSet { - let mut captured = HashSet::new(); + fn collect_captured_bindings(&self, stmts: &[Statement], own: &HashSet) -> BTreeMap { + let mut names = HashSet::new(); for stmt in stmts { - self.collect_captured_stmt(stmt, own, &mut captured); + self.collect_captured_stmt(stmt, own, &mut names); } - captured + // 名字排序分配 cell_idx(稳定跨 run,父 MAKE_CELL 与子 upvalue 统一引用) + let mut sorted: Vec = names.into_iter().collect(); + sorted.sort(); + sorted + .into_iter() + .enumerate() + .map(|(i, n)| (n, i as u8)) + .collect() } /// 只从嵌套函数节点进入扫描(本函数直接引用不算捕获)。 @@ -939,10 +960,14 @@ impl Compiler { match expr { Expression::FunctionExpression(fe) => { let body: &[Statement] = fe.body.as_ref().map(|b| &b.statements[..]).unwrap_or(&[]); - self.collect_capture_names(body, own, out); + let mut inner = self.collect_fn_param_names(&fe.params); + inner.extend(self.collect_own_binding_names(&[], body)); + self.collect_capture_names_shadowed(body, own, &inner, out); } Expression::ArrowFunctionExpression(ae) => { - self.collect_capture_names(&ae.body.statements, own, out); + let mut inner = self.collect_fn_param_names(&ae.params); + inner.extend(self.collect_own_binding_names(&[], &ae.body.statements)); + self.collect_capture_names_shadowed(&ae.body.statements, own, &inner, out); } Expression::CallExpression(ce) => { self.collect_captured_expr(&ce.callee, own, out); @@ -984,20 +1009,26 @@ impl Compiler { } /// 分析子函数 body:引用父级绑定的名字 → upvalue_captures(enclosing_reg 由父 emit 完成后填充)。 + /// 分析子函数 body:引用父级被捕获绑定的名字 → upvalue_captures。 + /// cell_idx 直接取父 captured_bindings 映射(父 emit 前已确定,索引一致); + /// enclosing_reg 由父 emit 完成后填充(assemble_ir)。 fn collect_upvalue_names( - &self, body_stmts: &[Statement], parent_own: &HashSet, sub_own: &HashSet, + &self, body_stmts: &[Statement], parent_captured: &BTreeMap, sub_own: &HashSet, ) -> Vec { + let parent_names: HashSet = parent_captured.keys().cloned().collect(); let mut names = HashSet::new(); - self.collect_capture_names_shadowed(body_stmts, parent_own, sub_own, &mut names); - let mut captures = Vec::with_capacity(names.len()); - for (cell_idx, name) in names.iter().enumerate() { - captures.push(UpvalueCapture { - name: name.clone(), - enclosing_reg: 0, // assemble_ir 时从父符号表填充 - cell_idx: cell_idx as u8, - }); - } - captures + self.collect_capture_names_shadowed(body_stmts, &parent_names, sub_own, &mut names); + names + .into_iter() + .map(|name| { + let cell_idx = parent_captured.get(&name).copied().unwrap_or(0); + UpvalueCapture { + name, + enclosing_reg: 0, // assemble_ir 时从父符号表填充 + cell_idx, + } + }) + .collect() } @@ -1496,13 +1527,8 @@ impl Compiler { // Free variable analysis for upvalue capture (Ordinary + Arrow functions only) if matches!(body_context, FunctionBodyContext::Ordinary | FunctionBodyContext::Arrow) { - let captures = self.collect_upvalue_names(body_stmts, &parent_ctx.own_bindings, &ctx.own_bindings); - ctx.current_upvalue_captures = captures; - ctx.scopes.cell_registry = ctx - .current_upvalue_captures - .iter() - .map(|u| (u.name.clone(), u.cell_idx)) - .collect(); + ctx.current_upvalue_captures = + self.collect_upvalue_names(body_stmts, &parent_ctx.captured_bindings, &ctx.own_bindings); } self.predeclare_function_declarations(body_stmts, &mut ctx); diff --git a/crates/oxide_compiler/src/emit_ctx.rs b/crates/oxide_compiler/src/emit_ctx.rs index 3732b25..25c0943 100644 --- a/crates/oxide_compiler/src/emit_ctx.rs +++ b/crates/oxide_compiler/src/emit_ctx.rs @@ -39,7 +39,6 @@ pub(crate) struct ScopeCtx { pub(crate) builtin_reg_map: Vec<(String, u8)>, pub(crate) private_name_map: Vec<(String, u32)>, pub(crate) next_private_name_id: u32, - pub(crate) cell_registry: Vec<(String, u8)>, } /// Reserved for destructuring-pattern state (Phase 15). Intentionally empty diff --git a/crates/oxide_compiler/src/expr/assignment.rs b/crates/oxide_compiler/src/expr/assignment.rs index 35e2add..d983742 100644 --- a/crates/oxide_compiler/src/expr/assignment.rs +++ b/crates/oxide_compiler/src/expr/assignment.rs @@ -146,14 +146,7 @@ impl Compiler { return Ok(val_reg); } // Check if target is a captured cell - if ctx.captured_bindings.contains(name) { - let cell_idx = ctx - .scopes - .cell_registry - .iter() - .find(|(n, _)| n == name) - .map(|(_, idx)| *idx) - .unwrap_or(0); + if let Some(&cell_idx) = ctx.captured_bindings.get(name) { ctx.inst(Inst::new(OpCode::CELL_SET, Operand::None, Operand::Reg(val_reg as u32), Operand::Imm(cell_idx as u16))); return Ok(val_reg); } diff --git a/crates/oxide_compiler/src/expr/identifier.rs b/crates/oxide_compiler/src/expr/identifier.rs index cbd1442..792ca00 100644 --- a/crates/oxide_compiler/src/expr/identifier.rs +++ b/crates/oxide_compiler/src/expr/identifier.rs @@ -18,19 +18,12 @@ impl Compiler { } } - if ctx.captured_bindings.contains(name) { - let cell_idx = ctx - .scopes - .cell_registry - .iter() - .find(|(n, _)| n == name) - .map(|(_, idx)| *idx) - .unwrap_or(0); + if let Some(&cell_idx) = ctx.captured_bindings.get(name) { let r = ctx.alloc_reg(); if let Some((binding, _)) = ctx.scopes.symbols.lookup_any_binding(name) { - ctx.inst(Inst::new(OpCode::CELL_GET, Operand::Reg(r as u32), Operand::Reg(binding.reg as u32), Operand::Reg(cell_idx as u32))); + ctx.inst(Inst::new(OpCode::CELL_GET, Operand::Reg(r as u32), Operand::Reg(binding.reg as u32), Operand::Imm(cell_idx as u16))); } else { - ctx.inst(Inst::new(OpCode::CELL_GET, Operand::Reg(r as u32), Operand::None, Operand::Reg(cell_idx as u32))); + ctx.inst(Inst::new(OpCode::CELL_GET, Operand::Reg(r as u32), Operand::None, Operand::Imm(cell_idx as u16))); } return Ok(r); } diff --git a/crates/oxide_compiler/src/expr/operator/mod.rs b/crates/oxide_compiler/src/expr/operator/mod.rs index 709ef8b..7ebb2a5 100644 --- a/crates/oxide_compiler/src/expr/operator/mod.rs +++ b/crates/oxide_compiler/src/expr/operator/mod.rs @@ -222,7 +222,7 @@ impl Compiler { let name = id.name.as_str(); // Check if this is an upvalue or captured cell reference let uv_idx = ctx.current_upvalue_captures.iter().position(|u| u.name == name); - let is_captured = ctx.captured_bindings.contains(name); + let captured_cell = ctx.captured_bindings.get(name).copied(); if let Some(uv) = uv_idx { // Upvalue: LOAD_UPVALUE + CONST(1) + ADD/SUB + STORE_UPVALUE let val_reg = ctx.alloc_reg(); @@ -238,15 +238,8 @@ impl Compiler { ctx.inst(Inst::new(op, Operand::Reg(val_reg as u32), Operand::Reg(val_reg as u32), Operand::Reg(one_reg as u32))); ctx.inst(Inst::new(OpCode::STORE_UPVALUE, Operand::None, Operand::Reg(val_reg as u32), Operand::Imm(uv as u16))); Ok(val_reg) - } else if is_captured { + } else if let Some(cell_idx) = captured_cell { // Captured cell: CELL_GET + CONST(1) + ADD/SUB + CELL_SET - let cell_idx = ctx - .scopes - .cell_registry - .iter() - .find(|(n, _)| n == name) - .map(|(_, idx)| *idx) - .unwrap_or(0); let val_reg = ctx.alloc_reg(); if let Some((binding, _)) = ctx.scopes.symbols.lookup_any_binding(name) { ctx.inst(Inst::new(OpCode::CELL_GET, Operand::Reg(val_reg as u32), Operand::Reg(binding.reg as u32), Operand::Imm(cell_idx as u16))); diff --git a/crates/oxide_compiler/src/ir/lower.rs b/crates/oxide_compiler/src/ir/lower.rs index 2577977..805d499 100644 --- a/crates/oxide_compiler/src/ir/lower.rs +++ b/crates/oxide_compiler/src/ir/lower.rs @@ -88,14 +88,16 @@ pub fn lower(f: &IRFunction) -> Result { }) } -/// Operand → u8 槽位。Const/Imm/Label 不在通用槽位路径(拆字/offset 单独处理)。 +/// Operand → u8 槽位。Const/Imm 在 a 槽走拆字(operand_pair_to_u8); +/// 出现在 b/rd 槽时按低字节直写(const_flag、cell_idx、uv_idx 等一字节立即数)。 fn operand_to_u8(o: &Operand) -> u8 { match o { Operand::Reg(r) => *r as u8, Operand::This => 254, Operand::NewTarget => 255, - Operand::None => 0, - Operand::Const(_) | Operand::Imm(_) | Operand::Label(_) => 0, + Operand::Imm(v) => (v & 0xFF) as u8, + Operand::Const(v) => (v & 0xFF) as u8, + Operand::Label(_) | Operand::None => 0, } } diff --git a/crates/oxide_compiler/src/shared/binding.rs b/crates/oxide_compiler/src/shared/binding.rs index 9d4a1e2..87d09c4 100644 --- a/crates/oxide_compiler/src/shared/binding.rs +++ b/crates/oxide_compiler/src/shared/binding.rs @@ -42,9 +42,7 @@ impl Compiler { ctx.declare(name, var_reg, kind, is_const)?; var_reg }; - if ctx.captured_bindings.contains(name) { - let cell_idx = ctx.scopes.cell_registry.len() as u8; - ctx.scopes.cell_registry.push((name.to_string(), cell_idx)); + if let Some(&cell_idx) = ctx.captured_bindings.get(name) { ctx.inst(Inst::new(OpCode::MAKE_CELL, Operand::Reg(src_reg as u32), Operand::Imm(cell_idx as u16), Operand::None)); } else { ctx.inst(Inst::new(OpCode::STORE_VAR, Operand::Reg(target_reg as u32), Operand::Reg(src_reg as u32), Operand::Imm(if is_const { 1 } else { 0 }))); diff --git a/crates/oxide_compiler/src/stmt/declaration/class_decl.rs b/crates/oxide_compiler/src/stmt/declaration/class_decl.rs index a25ef3c..1948fa9 100644 --- a/crates/oxide_compiler/src/stmt/declaration/class_decl.rs +++ b/crates/oxide_compiler/src/stmt/declaration/class_decl.rs @@ -20,7 +20,11 @@ impl Compiler { ctx.declare(&name, var_reg, VariableDeclarationKind::Let, false)?; ctx.init_var(&name); let ctor_reg = self.emit_class(class, ctx)?; - ctx.inst(Inst::new(OpCode::STORE_VAR, Operand::Reg(var_reg as u32), Operand::Reg(ctor_reg as u32), Operand::None)); + if let Some(&cell_idx) = ctx.captured_bindings.get(&name) { + ctx.inst(Inst::new(OpCode::MAKE_CELL, Operand::Reg(var_reg as u32), Operand::Imm(cell_idx as u16), Operand::None)); + } else { + ctx.inst(Inst::new(OpCode::STORE_VAR, Operand::Reg(var_reg as u32), Operand::Reg(ctor_reg as u32), Operand::None)); + } Ok(None) } } diff --git a/crates/oxide_compiler/src/stmt/declaration/function_decl.rs b/crates/oxide_compiler/src/stmt/declaration/function_decl.rs index bec73a0..6e373d3 100644 --- a/crates/oxide_compiler/src/stmt/declaration/function_decl.rs +++ b/crates/oxide_compiler/src/stmt/declaration/function_decl.rs @@ -35,7 +35,11 @@ impl Compiler { let var_reg = ctx.lookup(&name)?; ctx.reserve_reg(var_reg); ctx.inst(Inst::create_closure(Operand::Reg(var_reg as u32), ctx.nested.len() as u16)); - ctx.inst(Inst::new(OpCode::STORE_VAR, Operand::Reg(var_reg as u32), Operand::Reg(var_reg as u32), Operand::None)); + if let Some(&cell_idx) = ctx.captured_bindings.get(&name) { + ctx.inst(Inst::new(OpCode::MAKE_CELL, Operand::Reg(var_reg as u32), Operand::Imm(cell_idx as u16), Operand::None)); + } else { + ctx.inst(Inst::new(OpCode::STORE_VAR, Operand::Reg(var_reg as u32), Operand::Reg(var_reg as u32), Operand::None)); + } Ok(None) } } diff --git a/crates/oxide_compiler/src/stmt/declaration/var.rs b/crates/oxide_compiler/src/stmt/declaration/var.rs index dc45077..eee2ebb 100644 --- a/crates/oxide_compiler/src/stmt/declaration/var.rs +++ b/crates/oxide_compiler/src/stmt/declaration/var.rs @@ -46,10 +46,7 @@ impl Compiler { ctx.declare(bi.name.as_str(), var_reg, decl.kind, is_const)?; var_reg }; - let is_captured = ctx.captured_bindings.contains(bi.name.as_str()); - if is_captured { - let cell_idx = ctx.scopes.cell_registry.len() as u8; - ctx.scopes.cell_registry.push((bi.name.to_string(), cell_idx)); + if let Some(&cell_idx) = ctx.captured_bindings.get(bi.name.as_str()) { ctx.inst(Inst::new(OpCode::MAKE_CELL, Operand::Reg(tmp as u32), Operand::Imm(cell_idx as u16), Operand::None)); } else { ctx.inst(Inst::new(OpCode::STORE_VAR, Operand::Reg(target_reg as u32), Operand::Reg(tmp as u32), Operand::None)); diff --git a/crates/oxide_vm/tests/arrow_tests.rs b/crates/oxide_vm/tests/arrow_tests.rs index c68d967..7838bee 100644 --- a/crates/oxide_vm/tests/arrow_tests.rs +++ b/crates/oxide_vm/tests/arrow_tests.rs @@ -120,3 +120,11 @@ fn multiple_arrows_in_same_scope() { // // const o = {x:10, f:function(){ return (() => this.x)(); }}; o.f() // // Expected: 10 // } + +// -- Closure capture diagnostics -- + +#[test] +fn closure_captures_outer_let() { + assert_eq!(eval("var f; { let x = 7; f = () => x; } f()"), "7"); +} +