mirror of
https://github.com/pjh456/OxideJS.git
synced 2026-10-23 18:21:13 +08:00
fix: 完善闭包捕获(cell 索引稳定、参数遮蔽、函数/类名捕获、b 槽立即数)
This commit is contained in:
@@ -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 ¶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<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);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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)));
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user