diff --git a/crates/oxide_compiler/src/compiler.rs b/crates/oxide_compiler/src/compiler.rs index 7b34827..6292af6 100644 --- a/crates/oxide_compiler/src/compiler.rs +++ b/crates/oxide_compiler/src/compiler.rs @@ -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, }) } } diff --git a/crates/oxide_compiler/src/symbol_table.rs b/crates/oxide_compiler/src/symbol_table.rs index 22ffb62..b65848a 100644 --- a/crates/oxide_compiler/src/symbol_table.rs +++ b/crates/oxide_compiler/src/symbol_table.rs @@ -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, } 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), }); } diff --git a/crates/oxide_vm/src/dispatch/call.rs b/crates/oxide_vm/src/dispatch/call.rs index ff64e91..c29017b 100644 --- a/crates/oxide_vm/src/dispatch/call.rs +++ b/crates/oxide_vm/src/dispatch/call.rs @@ -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); + + 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); + self.frames.push(CallFrame { return_addr: self.pc, - function_name: self.sub_modules[sub_idx] - .function_name - .as_deref() - .map(|name| self.kernel_core.perm_interner().intern(name).0) - .unwrap_or(0), + 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); diff --git a/crates/oxide_vm/src/vm.rs b/crates/oxide_vm/src/vm.rs index 14da4b1..c328f70 100644 --- a/crates/oxide_vm/src/vm.rs +++ b/crates/oxide_vm/src/vm.rs @@ -140,6 +140,7 @@ pub(crate) struct InlineSyncState { pub(crate) saved_bytecode_stack: Vec>, pub(crate) saved_immutables_stack: Vec<*const [JsValue]>, pub(crate) save_stack: Vec, + pub(crate) cell_stack: Vec>, } 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>>, pub(crate) cell_stack: Vec>, /// 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) => {} diff --git a/crates/oxide_vm/src/vm_dispatch_ctrl.rs b/crates/oxide_vm/src/vm_dispatch_ctrl.rs index 5410395..69c1c43 100644 --- a/crates/oxide_vm/src/vm_dispatch_ctrl.rs +++ b/crates/oxide_vm/src/vm_dispatch_ctrl.rs @@ -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; diff --git a/crates/oxide_vm/src/vm_runtime.rs b/crates/oxide_vm/src/vm_runtime.rs index 82bbd67..3474634 100644 --- a/crates/oxide_vm/src/vm_runtime.rs +++ b/crates/oxide_vm/src/vm_runtime.rs @@ -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); diff --git a/crates/oxide_vm/src/vm_support.rs b/crates/oxide_vm/src/vm_support.rs index 24930f0..11f755a 100644 --- a/crates/oxide_vm/src/vm_support.rs +++ b/crates/oxide_vm/src/vm_support.rs @@ -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)");