fix: 修复字节码函数调用污染调用者寄存器问题

This commit is contained in:
爱特每羊队
2026-06-08 22:06:53 +08:00
parent 72f444ea20
commit a715a3ac21
4 changed files with 73 additions and 7 deletions
+10 -1
View File
@@ -56,6 +56,7 @@ pub(crate) struct CompileCtx {
pub(crate) constants: Vec<Constant>,
next_reg: u8,
pub(crate) max_regs: u8,
reserved_reg_start: u8,
symbols: SymbolTable,
pub(crate) label_map: HashMap<Label, usize>,
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,
+3
View File
@@ -19,6 +19,7 @@ pub struct CompiledModule {
pub constants: Vec<Constant>,
pub n_registers: u8,
pub n_args: u8,
pub param_base: u8,
pub builtin_reg_map: Vec<(String, u8)>,
pub sub_modules: Vec<CompiledModule>,
/// 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,
+38 -6
View File
@@ -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<String>,
#[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<JsValue, String> {
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;
+22
View File
@@ -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]