fix: 修复嵌套函数调用并接通闭包捕获链路

This commit is contained in:
爱特每羊队
2026-06-30 20:49:23 +08:00
parent 2d8e5d861d
commit 4271b7e348
7 changed files with 82 additions and 23 deletions
+21 -8
View File
@@ -1,3 +1,4 @@
use std::cell::Cell;
use std::collections::HashMap;
use crate::emit_ctx::{LabelCtx, ScopeCtx};
@@ -894,7 +895,7 @@ impl Compiler {
reg: binding.reg,
initialized: binding.initialized,
is_const: binding.is_const,
is_captured: binding.is_captured,
is_captured: binding.is_captured.clone(),
},
);
inherited_reg_start = inherited_reg_start.max(binding.reg.saturating_add(1));
@@ -906,7 +907,7 @@ impl Compiler {
reg: *reg,
initialized: true,
is_const: true,
is_captured: false,
is_captured: Cell::new(false),
},
);
inherited_reg_start = inherited_reg_start.max(reg.saturating_add(1));
@@ -918,8 +919,6 @@ impl Compiler {
ctx.reset_regs();
// Free variable analysis for upvalue capture (Ordinary + Arrow functions only)
// ponytail: temporarily disabled to isolate closure bug
/*
if matches!(body_context, FunctionBodyContext::Ordinary | FunctionBodyContext::Arrow) {
let (captures, _cells) = self.analyze_upvalue_captures(body_stmts, parent_ctx, &ctx.scopes.symbols);
ctx.current_upvalue_captures = captures;
@@ -932,7 +931,6 @@ impl Compiler {
}
}
}
*/
// Function body scope - params and local vars
ctx.push_scope_with_kind(ScopeKind::FunctionScope);
@@ -1010,9 +1008,20 @@ impl Compiler {
}
// Emit body statements.
// Capture the last statement's expression result for expression-body arrows.
// First sub-pass: emit function declarations (hoisting + marks parent captures)
let mut last_result_reg = None;
for stmt in body_stmts {
if matches!(stmt, Statement::FunctionDeclaration(_)) {
if let Some(reg) = self.emit_statement(stmt, &mut ctx)? {
last_result_reg = Some(reg);
}
}
}
// Second sub-pass: emit all other statements
for stmt in body_stmts {
if matches!(stmt, Statement::FunctionDeclaration(_)) {
continue;
}
if let Some(reg) = self.emit_statement(stmt, &mut ctx)? {
last_result_reg = Some(reg);
}
@@ -1079,7 +1088,9 @@ impl Compiler {
is_derived_constructor: false,
needs_home_object: false,
upvalue_captures: Vec::new(),
cells_needed: 0,
cells_needed: ctx.scopes.symbols.scopes[0].bindings.values()
.filter(|b| b.is_captured.get())
.count() as u8,
})
}
@@ -1179,7 +1190,9 @@ impl Compiler {
is_derived_constructor: false,
needs_home_object: false,
upvalue_captures: Vec::new(),
cells_needed: 0,
cells_needed: ctx.scopes.symbols.scopes[0].bindings.values()
.filter(|b| b.is_captured.get())
.count() as u8,
})
}
}
+7 -6
View File
@@ -1,3 +1,4 @@
use std::cell::Cell;
use std::collections::HashMap;
use oxide_parser::VariableDeclarationKind;
@@ -17,7 +18,7 @@ pub(crate) struct Binding {
pub(crate) reg: u8,
pub(crate) initialized: bool,
pub(crate) is_const: bool,
pub(crate) is_captured: bool,
pub(crate) is_captured: Cell<bool>,
}
pub struct SymbolTable {
@@ -85,7 +86,7 @@ impl SymbolTable {
reg,
initialized: false,
is_const: matches!(kind, VariableDeclarationKind::Const) || is_const,
is_captured: false,
is_captured: Cell::new(false),
},
);
Ok(())
@@ -131,7 +132,7 @@ impl SymbolTable {
reg: reg_for_new,
initialized: true,
is_const: false,
is_captured: false,
is_captured: Cell::new(false),
},
);
reg_for_new
@@ -152,7 +153,7 @@ impl SymbolTable {
pub fn lookup_is_captured(&self, name: &str) -> bool {
for scope in self.scopes.iter().rev() {
if let Some(b) = scope.bindings.get(name) {
return b.is_captured;
return b.is_captured.get();
}
}
false
@@ -178,7 +179,7 @@ impl SymbolTable {
reg,
initialized: true,
is_const: matches!(kind, VariableDeclarationKind::Const) || is_const,
is_captured: false,
is_captured: Cell::new(false),
},
);
Ok(())
@@ -189,7 +190,7 @@ impl SymbolTable {
reg,
initialized: true,
is_const: false,
is_captured: false,
is_captured: Cell::new(false),
});
}
+18 -5
View File
@@ -1,3 +1,5 @@
use std::sync::OnceLock;
use crate::native::NativeFn;
use crate::vm::{native_fn_ptr_to_fn, CallFrame, FrameContinuation, Vm};
use crate::{vm_debug, vm_trace};
@@ -346,13 +348,16 @@ impl Vm {
self.saved_bytecode_stack.push(std::mem::take(&mut self.bytecode));
self.saved_immutables_stack.push(self.active_immutables);
self.frames.push(CallFrame {
return_addr: self.pc,
function_name: self.sub_modules[sub_idx]
let function_name = self.sub_modules[sub_idx]
.function_name
.as_deref()
.map(|name| self.kernel_core.perm_interner().intern(name).0)
.unwrap_or(0),
.unwrap_or(0);
self.frames.push(CallFrame {
return_addr: self.pc,
function_name,
caller_reg_limit,
saved_reg_offset,
saved_this,
@@ -367,13 +372,21 @@ impl Vm {
self.bytecode = sub_bytecode;
let subs = Arc::clone(&self.sub_modules);
self.activate_immutables(sub_idx + 1, &subs[sub_idx].constants);
for (name, reg) in &self.sub_modules[sub_idx].builtin_reg_map {
self.cell_stack.push(Vec::with_capacity(subs[sub_idx].cells_needed as usize));
for (name, reg) in &self.sub_modules[sub_idx].builtin_reg_map.clone() {
let si = self.kernel_core.perm_interner().intern(name.as_str()).0;
let global = self.session.global_object();
if let Some(pos) = self.kernel_core.shape_forge().lookup_position(global.shape_id(), si) {
self.regs[*reg as usize] = global.get_prop_at(pos);
}
}
self.sub_module_stack.push(Arc::clone(&self.sub_modules));
let callee_subs = &subs[sub_idx].sub_modules;
if !callee_subs.is_empty() {
self.sub_modules = Arc::new(callee_subs.clone());
self.immutables_cache = (0..=callee_subs.len()).map(|_| OnceLock::new()).collect();
}
self.active_reg_limit = sub_n_registers.max(1);
self.pc = 0;
return Ok(true);
+27 -2
View File
@@ -140,6 +140,7 @@ pub(crate) struct InlineSyncState {
pub(crate) saved_bytecode_stack: Vec<Vec<opcode::Instr>>,
pub(crate) saved_immutables_stack: Vec<*const [JsValue]>,
pub(crate) save_stack: Vec<JsValue>,
pub(crate) cell_stack: Vec<Vec<*mut Cell>>,
}
pub struct Vm {
@@ -189,6 +190,7 @@ pub struct Vm {
pub(crate) iters: IterState,
/// Grouped inline-cache and instruction counters.
pub(crate) profiling: ProfilingState,
pub(crate) sub_module_stack: Vec<Arc<Vec<CompiledModule>>>,
pub(crate) cell_stack: Vec<Vec<*mut Cell>>,
/// Reusable string buffer for concatenation to avoid allocation per `+` op.
pub(crate) string_buf: String,
@@ -721,8 +723,6 @@ impl Vm {
self.saved_bytecode_stack.push(std::mem::take(&mut self.bytecode));
self.saved_immutables_stack.push(self.active_immutables);
// cell_stack push disabled for debug
// self.cell_stack.push(Vec::with_capacity(...));
let function_name = self.sub_modules[sub_idx]
.function_name
@@ -744,9 +744,11 @@ impl Vm {
continuation,
});
self.pc = 0;
self.bytecode = sub_bytecode;
let subs = Arc::clone(&self.sub_modules);
self.activate_immutables(sub_idx + 1, &subs[sub_idx].constants);
self.cell_stack.push(Vec::with_capacity(subs[sub_idx].cells_needed as usize));
for (name, reg) in &self.sub_modules[sub_idx].builtin_reg_map.clone() {
let si = self.kernel_core.perm_interner().intern(name.as_str()).0;
let global = self.session.global_object();
@@ -755,6 +757,14 @@ impl Vm {
}
}
self.sub_module_stack.push(Arc::clone(&self.sub_modules));
let callee_subs = &subs[sub_idx].sub_modules;
if !callee_subs.is_empty() {
self.sub_modules = Arc::new(callee_subs.clone());
self.immutables_cache = (0..=callee_subs.len()).map(|_| OnceLock::new()).collect();
}
self.cell_stack.push(Vec::with_capacity(subs[sub_idx].cells_needed as usize));
self.active_reg_limit = sub_n_registers.max(1);
self.pc = 0;
Ok(())
@@ -800,6 +810,21 @@ impl Vm {
OpCode::CREATE_CLOSURE => {
self.dispatch_create_closure(rd, instr);
}
OpCode::MAKE_CELL => {
self.dispatch_make_cell(rd, instr)?;
}
OpCode::CELL_GET => {
self.dispatch_cell_get(rd, a, b)?;
}
OpCode::CELL_SET => {
self.dispatch_cell_set(a, b)?;
}
OpCode::LOAD_UPVALUE => {
self.dispatch_load_upvalue(rd, instr)?;
}
OpCode::STORE_UPVALUE => {
self.dispatch_store_upvalue(a, b)?;
}
OpCode::CREATE_REGEXP => match self.dispatch_create_regexp(rd, a, b) {
Ok(Some(result)) => return Ok(result),
Ok(None) => {}
+1
View File
@@ -117,6 +117,7 @@ impl Vm {
self.frames.last().map(|f| f.return_addr).unwrap_or(0)
);
if let Some(frame) = self.frames.pop() {
self.cell_stack.pop();
let construct_result_reg = frame.construct_result_reg;
let constructed_this = frame.constructed_this;
let is_derived_constructor = frame.is_derived_constructor;
+5 -1
View File
@@ -60,6 +60,7 @@ impl Vm {
saved_bytecode_stack: std::mem::take(&mut self.saved_bytecode_stack),
saved_immutables_stack: std::mem::take(&mut self.saved_immutables_stack),
save_stack: std::mem::take(&mut self.save_stack),
cell_stack: std::mem::take(&mut self.cell_stack),
});
self.regs = [JsValue::undefined(); 256];
@@ -110,6 +111,7 @@ impl Vm {
self.saved_bytecode_stack = saved.saved_bytecode_stack;
self.saved_immutables_stack = saved.saved_immutables_stack;
self.save_stack = saved.save_stack;
self.cell_stack = saved.cell_stack;
result
}
@@ -126,6 +128,9 @@ impl Vm {
if let Some(saved_imm) = self.saved_immutables_stack.pop() {
self.active_immutables = saved_imm;
}
if let Some(saved_subs) = self.sub_module_stack.pop() {
self.sub_modules = saved_subs;
}
let offset = frame.saved_reg_offset as usize;
let len = frame.caller_reg_limit as usize;
self.regs[..len].copy_from_slice(&self.save_stack[offset..offset + len]);
@@ -148,7 +153,6 @@ impl Vm {
vm_debug!("run: starting bytecode execution, {} instructions", module.bytecode.len());
self.clear_execution_state();
self.sub_modules = Arc::new(module.sub_modules.clone());
// Per-run convert-once cache: slot 0 = top module, slot sub_idx+1 = sub_modules[sub_idx].
self.immutables_cache = (0..=self.sub_modules.len()).map(|_| OnceLock::new()).collect();
self.bytecode = module.bytecode.clone();
self.activate_immutables(0, &module.constants);
+2
View File
@@ -70,6 +70,7 @@ impl Vm {
instruction_count: 0,
},
string_buf: String::new(),
sub_module_stack: Vec::new(),
cell_stack: Vec::new(),
};
vm_info!("Vm created");
@@ -130,6 +131,7 @@ impl Vm {
instruction_count: 0,
},
string_buf: String::new(),
sub_module_stack: Vec::new(),
cell_stack: Vec::new(),
};
vm_info!("Vm created (pool)");