fix: 完善闭包捕获(cell 索引稳定、参数遮蔽、函数/类名捕获、b 槽立即数)

This commit is contained in:
2026-08-05 19:19:29 +08:00
parent 179960ed75
commit 050f75c952
11 changed files with 87 additions and 70 deletions
+56 -30
View File
@@ -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<UpvalueCapture>,
/// 本函数作用域声明的绑定名(参数 + 变量/函数声明,AST 收集,emit 前确定)。
pub(crate) own_bindings: HashSet<String>,
/// 本函数被嵌套函数捕获的绑定名(AST 分析,emit 前确定)。
/// 捕获判断(MAKE_CELL / CELL_GET / CELL_SET)统一查此集合,消除符号表时序依赖。
pub(crate) captured_bindings: HashSet<String>,
/// 本函数被嵌套函数捕获的绑定名 → cell_idx(名字排序分配,稳定跨 run)。
/// 捕获判断(MAKE_CELL / CELL_GET / CELL_SET)与子函数 upvalue cell_idx 统一查此映射,
/// 消除符号表时序依赖与 cell 索引错位。
pub(crate) captured_bindings: BTreeMap<String, u8>,
/// 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<String> {
let mut names = HashSet::new();
for p in &params.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<String> {
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<String>) -> HashSet<String> {
let mut captured = HashSet::new();
fn collect_captured_bindings(&self, stmts: &[Statement], own: &HashSet<String>) -> BTreeMap<String, u8> {
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<String> = 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<String>, sub_own: &HashSet<String>,
&self, body_stmts: &[Statement], parent_captured: &BTreeMap<String, u8>, sub_own: &HashSet<String>,
) -> Vec<UpvalueCapture> {
let parent_names: HashSet<String> = 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);
-1
View File
@@ -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
+1 -8
View File
@@ -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);
}
+3 -10
View File
@@ -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);
}
@@ -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)));
+5 -3
View File
@@ -88,14 +88,16 @@ pub fn lower(f: &IRFunction) -> Result<CompiledModule, String> {
})
}
/// 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,
}
}
+1 -3
View File
@@ -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 })));
@@ -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)
}
}
@@ -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)
}
}
@@ -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));
+8
View File
@@ -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");
}