diff --git a/crates/oxide_compiler/src/compiler.rs b/crates/oxide_compiler/src/compiler.rs index e1e9c96..176f54e 100644 --- a/crates/oxide_compiler/src/compiler.rs +++ b/crates/oxide_compiler/src/compiler.rs @@ -700,10 +700,15 @@ impl Compiler { match expr { Expression::Identifier(ident) => { let name = ident.name.as_str(); - // Only skip if name is in the nested function's OWN scopes (not inherited scopes[0]) - let is_local = nested_symbols.scopes.iter().skip(1).any(|s| s.bindings.contains_key(name)); - if is_local { - return; + if nested_symbols.lookup_any_binding(name).is_some() { + // Check if this is a parent function-scope binding (real upvalue) + let is_parent_func_var = parent_ctx.scopes.symbols.scopes.iter() + .skip(1) + .any(|s| s.bindings.contains_key(name)); + if !is_parent_func_var { + return; // true local or global, not upvalue + } + // falls through to capture } if let Some((binding, _)) = parent_ctx.scopes.symbols.lookup_any_binding(name) { if seen.contains_key(name) { @@ -721,8 +726,11 @@ impl Compiler { Expression::AssignmentExpression(ae) => { if let oxide_parser::AssignmentTarget::AssignmentTargetIdentifier(ati) = &ae.left { let name = ati.name.as_str(); - let is_local = nested_symbols.scopes.iter().skip(1).any(|s| s.bindings.contains_key(name)); - if !is_local { +let in_nested = nested_symbols.lookup_any_binding(name).is_some(); +let is_parent_func_var = parent_ctx.scopes.symbols.scopes.iter() + .skip(1) + .any(|s| s.bindings.contains_key(name)); +if !in_nested || is_parent_func_var { if let Some((binding, _)) = parent_ctx.scopes.symbols.lookup_any_binding(name) { if !seen.contains_key(name) { let cell_idx = captures.len() as u8; @@ -748,8 +756,11 @@ impl Compiler { Expression::UpdateExpression(ue) => { if let oxide_parser::SimpleAssignmentTarget::AssignmentTargetIdentifier(ati) = &ue.argument { let name = ati.name.as_str(); - let is_local = nested_symbols.scopes.iter().skip(1).any(|s| s.bindings.contains_key(name)); - if !is_local { +let in_nested = nested_symbols.lookup_any_binding(name).is_some(); +let is_parent_func_var = parent_ctx.scopes.symbols.scopes.iter() + .skip(1) + .any(|s| s.bindings.contains_key(name)); +if !in_nested || is_parent_func_var { if let Some((binding, _)) = parent_ctx.scopes.symbols.lookup_any_binding(name) { if !seen.contains_key(name) { let cell_idx = captures.len() as u8; diff --git a/crates/oxide_vm/src/dispatch/call.rs b/crates/oxide_vm/src/dispatch/call.rs index 5c94607..1d736b9 100644 --- a/crates/oxide_vm/src/dispatch/call.rs +++ b/crates/oxide_vm/src/dispatch/call.rs @@ -195,7 +195,18 @@ impl Vm { let obj = unsafe { &mut *callee.as_js_object_ptr() }; let upvals = obj.upvalues_slice_mut(); if uv_idx < upvals.len() { - let cell = self.gc_state.session_epoch.alloc(Cell::new(self.regs[rd], true)); + // Try to get value from caller's cell table (MAKE_CELL ran after CREATE_CLOSURE) + let val = if self.cell_stack.len() >= 2 { + let caller_cells = &self.cell_stack[self.cell_stack.len() - 2]; + if uv_idx < caller_cells.len() && !caller_cells[uv_idx].is_null() { + unsafe { (*caller_cells[uv_idx]).value } + } else { + self.regs[rd] + } + } else { + self.regs[rd] + }; + let cell = self.gc_state.session_epoch.alloc(Cell::new(val, true)); upvals[uv_idx] = cell as *mut Cell; self.regs[rd] = cell.value; return Ok(()); diff --git a/crates/oxide_vm/src/vm.rs b/crates/oxide_vm/src/vm.rs index 87e3b42..83975ef 100644 --- a/crates/oxide_vm/src/vm.rs +++ b/crates/oxide_vm/src/vm.rs @@ -767,7 +767,6 @@ impl Vm { 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); diff --git a/crates/oxide_vm/src/vm_runtime.rs b/crates/oxide_vm/src/vm_runtime.rs index 3474634..4ed2f8e 100644 --- a/crates/oxide_vm/src/vm_runtime.rs +++ b/crates/oxide_vm/src/vm_runtime.rs @@ -152,6 +152,8 @@ impl Vm { pub fn run(&mut self, module: &CompiledModule) -> Result { vm_debug!("run: starting bytecode execution, {} instructions", module.bytecode.len()); self.clear_execution_state(); + self.cell_stack.clear(); + self.cell_stack.push(Vec::new()); self.sub_modules = Arc::new(module.sub_modules.clone()); self.immutables_cache = (0..=self.sub_modules.len()).map(|_| OnceLock::new()).collect(); self.bytecode = module.bytecode.clone(); diff --git a/crates/oxide_vm/src/vm_support.rs b/crates/oxide_vm/src/vm_support.rs index 11f755a..4b9d331 100644 --- a/crates/oxide_vm/src/vm_support.rs +++ b/crates/oxide_vm/src/vm_support.rs @@ -205,6 +205,7 @@ impl Vm { self.saved_bytecode_stack.clear(); self.saved_immutables_stack.clear(); self.save_stack.clear(); + self.cell_stack.clear(); self.try_stack.clear(); self.exception_value = None; self.pending_exception = None;