From 0a87aa2ac388b04d1bff464ab704c55148be7461 Mon Sep 17 00:00:00 2001 From: pjh456 <147148383@qq.com> Date: Mon, 8 Jun 2026 22:06:53 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=E5=AD=97=E8=8A=82?= =?UTF-8?q?=E7=A0=81=E5=87=BD=E6=95=B0=E8=B0=83=E7=94=A8=E6=B1=A1=E6=9F=93?= =?UTF-8?q?=E8=B0=83=E7=94=A8=E8=80=85=E5=AF=84=E5=AD=98=E5=99=A8=E9=97=AE?= =?UTF-8?q?=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- crates/oxide_compiler/src/compiler.rs | 11 ++++++- crates/oxide_compiler/src/module.rs | 3 ++ crates/oxide_vm/src/vm.rs | 44 +++++++++++++++++++++---- crates/oxide_vm/tests/function_tests.rs | 22 +++++++++++++ 4 files changed, 73 insertions(+), 7 deletions(-) diff --git a/crates/oxide_compiler/src/compiler.rs b/crates/oxide_compiler/src/compiler.rs index 8624e018..c7ab89a6 100644 --- a/crates/oxide_compiler/src/compiler.rs +++ b/crates/oxide_compiler/src/compiler.rs @@ -56,6 +56,7 @@ pub(crate) struct CompileCtx { pub(crate) constants: Vec, next_reg: u8, pub(crate) max_regs: u8, + reserved_reg_start: u8, symbols: SymbolTable, pub(crate) label_map: HashMap, pub(crate) loop_stack: Vec<(Label, Label)>, @@ -78,6 +79,7 @@ impl CompileCtx { constants: Vec::new(), next_reg: 0, max_regs: 0, + reserved_reg_start: 0, symbols: SymbolTable::new(), label_map: HashMap::new(), loop_stack: Vec::new(), @@ -104,7 +106,7 @@ impl CompileCtx { } pub(crate) fn reset_regs(&mut self) { - self.next_reg = self.builtin_reg_map.len() as u8; + self.next_reg = (self.builtin_reg_map.len() as u8).max(self.reserved_reg_start); self.projected_pc = 0; self.label_counter = 0; } @@ -275,6 +277,7 @@ impl Compiler { // Inherit parent's global scope entries so previously-declared function names // are visible from within the body. + let mut inherited_reg_start = ctx.builtin_reg_map.len() as u8; for (name, binding) in &parent_ctx.symbols.scopes[0].bindings { ctx.symbols.scopes[0].bindings.insert( name.clone(), @@ -284,7 +287,9 @@ impl Compiler { is_const: binding.is_const, }, ); + inherited_reg_start = inherited_reg_start.max(binding.reg.saturating_add(1)); } + ctx.reserved_reg_start = inherited_reg_start; // Align next_reg with builtin count so both count and emit passes start at the // same register offset (params go after builtin slots). @@ -293,6 +298,8 @@ impl Compiler { // Function body scope - params and local vars ctx.push_scope_with_kind(ScopeKind::FunctionScope); + let param_base = ctx.next_reg; + // Register parameters as initialized. for name in param_names { let reg = ctx.alloc_reg(); @@ -354,6 +361,7 @@ impl Compiler { constants: ctx.constants, n_registers: ctx.max_regs, n_args: param_names.len() as u8, + param_base, builtin_reg_map: ctx.builtin_reg_map, sub_modules: ctx.sub_modules, is_arrow: false, @@ -410,6 +418,7 @@ impl Compiler { constants: ctx.constants, n_registers: ctx.max_regs, n_args: 0, + param_base: ctx.builtin_reg_map.len() as u8, builtin_reg_map: ctx.builtin_reg_map, sub_modules: ctx.sub_modules, is_arrow: false, diff --git a/crates/oxide_compiler/src/module.rs b/crates/oxide_compiler/src/module.rs index 2772fcc8..61e53510 100644 --- a/crates/oxide_compiler/src/module.rs +++ b/crates/oxide_compiler/src/module.rs @@ -19,6 +19,7 @@ pub struct CompiledModule { pub constants: Vec, pub n_registers: u8, pub n_args: u8, + pub param_base: u8, pub builtin_reg_map: Vec<(String, u8)>, pub sub_modules: Vec, /// True when this module is an arrow function body (D-01). @@ -39,6 +40,7 @@ impl CompiledModule { constants: Vec::new(), n_registers: 0, n_args: 0, + param_base: 0, builtin_reg_map: Vec::new(), sub_modules: Vec::new(), is_arrow: false, @@ -61,6 +63,7 @@ impl Clone for CompiledModule { constants: self.constants.clone(), n_registers: self.n_registers, n_args: self.n_args, + param_base: self.param_base, builtin_reg_map: self.builtin_reg_map.clone(), sub_modules: self.sub_modules.clone(), is_arrow: self.is_arrow, diff --git a/crates/oxide_vm/src/vm.rs b/crates/oxide_vm/src/vm.rs index 88fd5856..c974d806 100644 --- a/crates/oxide_vm/src/vm.rs +++ b/crates/oxide_vm/src/vm.rs @@ -140,6 +140,10 @@ pub struct CallFrame { pub function_obj_reg: u8, pub frame_base: u8, pub function_name: u32, + pub caller_reg_limit: u8, + pub saved_regs: Box<[JsValue]>, + pub saved_this: JsValue, + pub saved_new_target: JsValue, } pub struct ForInIter<'bump> { @@ -177,6 +181,8 @@ pub struct Vm { pub(crate) symbol_descriptions: Vec, #[allow(dead_code)] pub(crate) for_of_iters: Vec<*mut u8>, + pub(crate) root_reg_limit: u8, + pub(crate) active_reg_limit: u8, } impl Vm { @@ -207,6 +213,8 @@ impl Vm { symbol_counter: 0, symbol_descriptions: Vec::new(), for_of_iters: Vec::new(), + root_reg_limit: 0, + active_reg_limit: 0, } } @@ -235,6 +243,8 @@ impl Vm { symbol_counter: 0, symbol_descriptions: Vec::new(), for_of_iters: Vec::new(), + root_reg_limit: 0, + active_reg_limit: 0, } } @@ -288,6 +298,8 @@ impl Vm { self.for_in_iters.clear(); self.epoch.reset(); self.interned_strings.clear(); + self.root_reg_limit = 0; + self.active_reg_limit = 0; } pub fn intern(&mut self, s: &str) -> JsValue { @@ -463,6 +475,7 @@ impl Vm { pub fn rerun(&mut self) -> Result { self.pc = 0; self.regs = [JsValue::undefined(); 256]; + self.active_reg_limit = self.root_reg_limit; self.frames.clear(); self.for_in_iters.clear(); self.try_stack.clear(); @@ -497,6 +510,8 @@ impl Vm { self.bytecode = module.bytecode.clone(); self.pc = 0; self.regs = [JsValue::undefined(); 256]; + self.root_reg_limit = module.n_registers.max(1); + self.active_reg_limit = self.root_reg_limit; self.frames.clear(); self.saved_bytecode_stack.clear(); self.saved_constants_stack.clear(); @@ -729,16 +744,22 @@ impl Vm { // Clone sub_module data before mutably borrowing self let sub_bytecode = self.sub_modules[sub_idx].bytecode.clone(); let sub_n_args = self.sub_modules[sub_idx].n_args as usize; + let sub_n_registers = self.sub_modules[sub_idx].n_registers; let sub_constants = self.sub_modules[sub_idx].constants.clone(); - let sub_builtin_count = - self.sub_modules[sub_idx].builtin_reg_map.len(); + let sub_param_base = + self.sub_modules[sub_idx].param_base as usize; let sub_is_arrow = self.sub_modules[sub_idx].is_arrow; + let caller_reg_limit = self.active_reg_limit.max(1); + let saved_regs = self.regs[..caller_reg_limit as usize] + .to_vec() + .into_boxed_slice(); + let saved_this = self.regs[254]; + let saved_new_target = self.regs[255]; - // Copy args to regs[sub_builtin_count..sub_builtin_count+n_args] - // (builtins occupy regs[0..sub_builtin_count-1]) + // Copy args into the callee's parameter register window. for i in 0..sub_n_args { let src_reg = first_arg_reg.wrapping_add(i as u8) as usize; - self.regs[sub_builtin_count + i] = self.regs[src_reg]; + self.regs[sub_param_base + i] = self.regs[src_reg]; } // Set this (reg 254 convention). // Arrow functions use captured lexical `this` (D-01). @@ -747,6 +768,7 @@ impl Vm { } else { callee }; + self.regs[255] = JsValue::undefined(); // Save current bytecode/constants self.saved_bytecode_stack @@ -760,8 +782,12 @@ impl Vm { n_locals: sub_n_args as u8, n_args: sub_n_args as u8, function_obj_reg: callee_reg as u8, - frame_base: sub_builtin_count as u8, + frame_base: sub_param_base as u8, function_name: 0, + caller_reg_limit, + saved_regs, + saved_this, + saved_new_target, }); // Convert sub_module constants @@ -804,6 +830,7 @@ impl Vm { } } + self.active_reg_limit = sub_n_registers.max(1); self.pc = 0; continue; } @@ -944,6 +971,11 @@ impl Vm { if let Some(saved_consts) = self.saved_constants_stack.pop() { self.constants = saved_consts; } + let restore_len = frame.saved_regs.len(); + self.regs[..restore_len].copy_from_slice(&frame.saved_regs); + self.regs[254] = frame.saved_this; + self.regs[255] = frame.saved_new_target; + self.active_reg_limit = frame.caller_reg_limit; // Restore caller's pc and merge result into caller's regs[0]. self.pc = frame.return_addr; self.regs[0] = result; diff --git a/crates/oxide_vm/tests/function_tests.rs b/crates/oxide_vm/tests/function_tests.rs index 4ba077ae..7aaad7e4 100644 --- a/crates/oxide_vm/tests/function_tests.rs +++ b/crates/oxide_vm/tests/function_tests.rs @@ -94,6 +94,28 @@ fn two_funcs_called_from_global() { assert!((result.as_double() - 3.0).abs() < 0.0001); } +#[test] +fn call_preserves_previous_call_result_register() { + let mut vm = Vm::new(); + let result = eval( + &mut vm, + "function g() { return 10; } function h() { return 20; } function f() { return g() + h(); } f()", + ) + .unwrap(); + assert!((result.as_double() - 30.0).abs() < 0.0001); +} + +#[test] +fn call_preserves_local_across_nested_bytecode_call() { + let mut vm = Vm::new(); + let result = eval( + &mut vm, + "function g() { return 10; } function h() { return 20; } function f() { var x = g(); return x + h(); } f()", + ) + .unwrap(); + assert!((result.as_double() - 30.0).abs() < 0.0001); +} + // --- Builtins inside functions --- #[test]