fix: 降低函数体寄存器压力

This commit is contained in:
爱特每羊队
2026-06-15 00:33:58 +08:00
parent 98cc47c212
commit fdaedc060b
6 changed files with 201 additions and 116 deletions
+114 -83
View File
@@ -17,17 +17,84 @@ pub(crate) fn is_int_literal(value: f64) -> bool {
}
pub(crate) fn is_side_effect_free(expr: &Expression) -> bool {
matches!(
expr,
match expr {
Expression::NumericLiteral(_)
| Expression::StringLiteral(_)
| Expression::BooleanLiteral(_)
| Expression::NullLiteral(_)
| Expression::Identifier(_)
| Expression::RegExpLiteral(_)
)
| Expression::StringLiteral(_)
| Expression::BooleanLiteral(_)
| Expression::NullLiteral(_)
| Expression::Identifier(_)
| Expression::RegExpLiteral(_)
| Expression::ThisExpression(_) => true,
Expression::ParenthesizedExpression(p) => is_side_effect_free(&p.expression),
Expression::BinaryExpression(bin) => is_side_effect_free(&bin.left) && is_side_effect_free(&bin.right),
Expression::UnaryExpression(un) => {
!matches!(un.operator, UnaryOperator::Delete) && is_side_effect_free(&un.argument)
}
_ => false,
}
}
const BUILTIN_GLOBALS: &[&str] = &[
"NaN",
"undefined",
"Infinity",
"globalThis",
"Object",
"Array",
"String",
"Number",
"Boolean",
"Function",
"Error",
"TypeError",
"ReferenceError",
"RangeError",
"SyntaxError",
"URIError",
"EvalError",
"Math",
"JSON",
"Promise",
"Date",
"Set",
"Map",
"RegExp",
"Symbol",
"parseInt",
"parseFloat",
"isNaN",
"isFinite",
"Proxy",
"WeakMap",
"WeakSet",
"WeakRef",
"FinalizationRegistry",
"Atomics",
"SharedArrayBuffer",
"ArrayBuffer",
"DataView",
"Iterator",
"BigInt",
"Int8Array",
"Uint8Array",
"Uint8ClampedArray",
"Int16Array",
"Uint16Array",
"Int32Array",
"Uint32Array",
"Float32Array",
"Float64Array",
"BigInt64Array",
"BigUint64Array",
"Reflect",
"escape",
"unescape",
"encodeURI",
"decodeURI",
"encodeURIComponent",
"decodeURIComponent",
];
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[allow(dead_code)]
pub(crate) enum Label {
@@ -122,9 +189,9 @@ impl CompileCtx {
bytecode: Vec::new(),
constants: Vec::new(),
constant_map: HashMap::new(),
next_reg: 0,
max_regs: 0,
reserved_reg_start: 0,
next_reg: 1,
max_regs: 1,
reserved_reg_start: 1,
symbols: SymbolTable::new(),
label_map: HashMap::new(),
loop_stack: Vec::new(),
@@ -167,7 +234,7 @@ impl CompileCtx {
}
pub(crate) fn reset_regs(&mut self) {
self.next_reg = (self.builtin_reg_map.len() as u8).max(self.reserved_reg_start);
self.next_reg = self.builtin_reg_floor().max(self.reserved_reg_start);
self.projected_pc = 0;
self.label_counter = 0;
}
@@ -243,7 +310,23 @@ impl CompileCtx {
self.symbols.lookup(name)
}
pub(crate) fn lookup_or_builtin(&mut self, name: &str) -> Result<u8, String> {
match self.symbols.lookup(name) {
Ok(reg) => Ok(reg),
Err(err) if Self::is_known_builtin(name) && err.contains("is not defined") => {
let reg = self.alloc_reg();
self.symbols.pre_register_global(name, reg);
self.builtin_reg_map.push((name.to_string(), reg));
Ok(reg)
}
Err(err) => Err(err),
}
}
pub(crate) fn lookup_or_global(&mut self, name: &str) -> u8 {
if let Some(reg) = self.symbols.lookup_any(name) {
return reg;
}
let reg = self.alloc_reg();
self.symbols.lookup_or_global(name, reg)
}
@@ -290,76 +373,22 @@ impl CompileCtx {
self.builtin_reg_map.iter().any(|(n, _)| n == name)
}
pub(crate) fn is_known_builtin(name: &str) -> bool {
BUILTIN_GLOBALS.contains(&name)
}
fn builtin_reg_floor(&self) -> u8 {
self.builtin_reg_map
.iter()
.map(|(_, reg)| reg.saturating_add(1))
.max()
.unwrap_or(0)
}
pub(crate) fn pre_register_builtins(&mut self) {
let builtins = [
"NaN",
"undefined",
"Infinity",
"globalThis",
"Object",
"Array",
"String",
"Number",
"Boolean",
"Function",
"Error",
"TypeError",
"ReferenceError",
"RangeError",
"SyntaxError",
"URIError",
"EvalError",
"Math",
"JSON",
"Promise",
"Date",
"Set",
"Map",
"RegExp",
"Symbol",
"parseInt",
"parseFloat",
"isNaN",
"isFinite",
// Out-of-scope constructors: bound as TypeError-throwing stubs
"Proxy",
"WeakMap",
"WeakSet",
"WeakRef",
"FinalizationRegistry",
"Atomics",
"SharedArrayBuffer",
"ArrayBuffer",
"DataView",
"Iterator",
"BigInt",
// TypedArray family stubs
"Int8Array",
"Uint8Array",
"Uint8ClampedArray",
"Int16Array",
"Uint16Array",
"Int32Array",
"Uint32Array",
"Float32Array",
"Float64Array",
"BigInt64Array",
"BigUint64Array",
// Reflect namespace object
"Reflect",
// URI utility functions
"escape",
"unescape",
"encodeURI",
"decodeURI",
"encodeURIComponent",
"decodeURIComponent",
];
for name in &builtins {
let reg = self.alloc_reg();
self.symbols.pre_register_global(name, reg);
self.builtin_reg_map.push((name.to_string(), reg));
}
// Builtin globals are resolved lazily by lookup_or_builtin(). Keeping this
// hook preserves the compile pipeline without reserving ~60 registers in
// every module.
}
}
@@ -456,7 +485,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;
let mut inherited_reg_start = 1u8.max(ctx.builtin_reg_floor());
for (name, binding) in &parent_ctx.symbols.scopes[0].bindings {
ctx.symbols.scopes[0].bindings.insert(
name.clone(),
@@ -479,7 +508,7 @@ impl Compiler {
);
inherited_reg_start = inherited_reg_start.max(reg.saturating_add(1));
}
ctx.reserved_reg_start = inherited_reg_start;
ctx.reserved_reg_start = inherited_reg_start.max(1);
// Align next_reg with builtin count so both count and emit passes start at the
// same register offset (params go after builtin slots).
@@ -502,6 +531,7 @@ impl Compiler {
self.count_statement(stmt, &mut ctx);
}
ctx.max_regs = ctx.max_regs.max(1);
ctx.reg_overflow = false;
ctx.reset_regs();
// Emit pass - reallocate params (same order = same regs after reset)
@@ -584,6 +614,7 @@ impl Compiler {
self.count_statement(stmt, &mut ctx);
}
ctx.max_regs = ctx.max_regs.max(1);
ctx.reg_overflow = false;
ctx.reset_regs();
// First sub-pass: emit FunctionDeclarations (hoisting)
+15 -4
View File
@@ -446,10 +446,13 @@ impl Compiler {
pub(crate) fn count_expression(&self, expr: &Expression, ctx: &mut CompileCtx) {
match expr {
Expression::BinaryExpression(bin) => {
let checkpoint = ctx.reg_checkpoint();
self.count_expression(&bin.left, ctx);
self.count_expression(&bin.right, ctx);
ctx.alloc_reg();
ctx.projected_pc += 1; // ADD/SUB/MUL/DIV/etc.
if is_side_effect_free(&bin.left) && is_side_effect_free(&bin.right) {
ctx.restore_reg_checkpoint(checkpoint.saturating_add(1));
}
}
Expression::UnaryExpression(un) => {
match un.operator {
@@ -460,13 +463,11 @@ impl Compiler {
}
Expression::StaticMemberExpression(member) => {
self.count_expression(&member.object, ctx);
ctx.alloc_reg();
ctx.projected_pc += 2; // DELETE_PROP_STATIC + ext word
}
Expression::ComputedMemberExpression(member) => {
self.count_expression(&member.object, ctx);
self.count_expression(&member.expression, ctx);
ctx.alloc_reg();
ctx.projected_pc += 1; // DELETE_PROP_DYNAMIC
}
_ => {}
@@ -474,7 +475,6 @@ impl Compiler {
}
_ => {
self.count_expression(&un.argument, ctx);
ctx.alloc_reg();
ctx.projected_pc += 1; // NEG/TYPEOF/VOID/NOT
}
}
@@ -602,6 +602,7 @@ impl Compiler {
Expression::ObjectExpression(obj) => {
ctx.alloc_reg();
ctx.projected_pc += 1; // NEW_OBJECT
let prop_checkpoint = ctx.reg_checkpoint();
for prop in &obj.properties {
if let oxide_parser::ObjectPropertyKind::ObjectProperty(p) = prop {
if matches!(p.kind, oxide_parser::PropertyKind::Get | oxide_parser::PropertyKind::Set) {
@@ -614,6 +615,7 @@ impl Compiler {
self.count_expression(&p.value, ctx);
ctx.projected_pc += 1; // SET_PROP
}
ctx.restore_reg_checkpoint(prop_checkpoint);
}
}
}
@@ -713,12 +715,14 @@ impl Compiler {
Expression::ArrayExpression(arr) => {
ctx.alloc_reg();
ctx.projected_pc += 1; // NEW_ARRAY
let elem_checkpoint = ctx.reg_checkpoint();
for elem in &arr.elements {
if let Some(e) = elem.as_expression() {
self.count_expression(e, ctx);
ctx.alloc_reg();
ctx.projected_pc += 1; // LOAD_CONST index
ctx.projected_pc += 1; // SET_ELEM
ctx.restore_reg_checkpoint(elem_checkpoint);
}
}
}
@@ -751,6 +755,13 @@ impl Compiler {
ctx.alloc_reg();
ctx.projected_pc += 1; // LOAD_VAR from reg 254
}
Expression::Identifier(ident) => {
if CompileCtx::is_known_builtin(ident.name.as_str()) {
let _ = ctx.lookup_or_builtin(ident.name.as_str());
}
ctx.alloc_reg();
ctx.projected_pc += 1; // LOAD_VAR
}
Expression::UpdateExpression(update) => match &update.argument {
SimpleAssignmentTarget::AssignmentTargetIdentifier(_) => {
ctx.alloc_reg();
+29 -29
View File
@@ -147,7 +147,7 @@ impl Compiler {
match key {
PropertyKey::Identifier(ident) => {
let name = ident.name.as_str();
let var_reg = ctx.lookup(name)?;
let var_reg = ctx.lookup_or_builtin(name)?;
let key_reg = ctx.alloc_reg();
ctx.emit(opcode::encode(OpCode::LOAD_VAR, key_reg, var_reg, 0));
Ok(key_reg)
@@ -1097,6 +1097,7 @@ impl Compiler {
Ok(r)
}
Expression::BinaryExpression(bin) => {
let checkpoint = ctx.reg_checkpoint();
let left = self.emit_expression(&bin.left, ctx)?;
let right = self.emit_expression(&bin.right, ctx)?;
let op = match bin.operator {
@@ -1123,42 +1124,38 @@ impl Compiler {
BinaryOperator::StrictInequality => OpCode::STRICT_NEQ,
_ => return Err(format!("unsupported binary operator: {:?}", bin.operator)),
};
let r = ctx.alloc_reg();
ctx.emit(opcode::encode(op, r, left, right));
Ok(r)
ctx.emit(opcode::encode(op, left, left, right));
if is_side_effect_free(&bin.left) && is_side_effect_free(&bin.right) {
ctx.restore_reg_checkpoint(checkpoint.saturating_add(1).max(left.saturating_add(1)));
}
Ok(left)
}
Expression::UnaryExpression(un) => {
let arg = self.emit_expression(&un.argument, ctx)?;
match un.operator {
UnaryOperator::UnaryNegation => {
let r = ctx.alloc_reg();
ctx.emit(opcode::encode(OpCode::NEG, r, arg, 0));
Ok(r)
ctx.emit(opcode::encode(OpCode::NEG, arg, arg, 0));
Ok(arg)
}
UnaryOperator::Typeof => {
let r = ctx.alloc_reg();
ctx.emit(opcode::encode(OpCode::TYPEOF, r, arg, 0));
Ok(r)
ctx.emit(opcode::encode(OpCode::TYPEOF, arg, arg, 0));
Ok(arg)
}
UnaryOperator::Void => {
let r = ctx.alloc_reg();
ctx.emit(opcode::encode(OpCode::VOID, r, arg, 0));
Ok(r)
ctx.emit(opcode::encode(OpCode::VOID, arg, arg, 0));
Ok(arg)
}
UnaryOperator::LogicalNot => {
let r = ctx.alloc_reg();
ctx.emit(opcode::encode(OpCode::NOT, r, arg, 0));
Ok(r)
ctx.emit(opcode::encode(OpCode::NOT, arg, arg, 0));
Ok(arg)
}
UnaryOperator::BitwiseNot => {
let r = ctx.alloc_reg();
ctx.emit(opcode::encode(OpCode::BIT_NOT, r, arg, 0));
Ok(r)
ctx.emit(opcode::encode(OpCode::BIT_NOT, arg, arg, 0));
Ok(arg)
}
UnaryOperator::UnaryPlus => {
let r = ctx.alloc_reg();
ctx.emit(opcode::encode(OpCode::UNARY_PLUS, r, arg, 0));
Ok(r)
ctx.emit(opcode::encode(OpCode::UNARY_PLUS, arg, arg, 0));
Ok(arg)
}
UnaryOperator::Delete => match &un.argument {
Expression::Identifier(_) => {
@@ -1168,17 +1165,15 @@ impl Compiler {
let obj_reg = self.emit_expression(&member.object, ctx)?;
let prop_name = member.property.name.as_str();
let const_idx = ctx.add_constant(Constant::String(prop_name.to_string()));
let r = ctx.alloc_reg();
ctx.emit(opcode::encode(OpCode::DELETE_PROP_STATIC, r, obj_reg, 0));
ctx.emit(opcode::encode(OpCode::DELETE_PROP_STATIC, obj_reg, obj_reg, 0));
ctx.emit(const_idx as u32);
Ok(r)
Ok(obj_reg)
}
Expression::ComputedMemberExpression(member) => {
let obj_reg = self.emit_expression(&member.object, ctx)?;
let key_reg = self.emit_expression(&member.expression, ctx)?;
let r = ctx.alloc_reg();
ctx.emit(opcode::encode(OpCode::DELETE_PROP_DYNAMIC, r, obj_reg, key_reg));
Ok(r)
ctx.emit(opcode::encode(OpCode::DELETE_PROP_DYNAMIC, obj_reg, obj_reg, key_reg));
Ok(obj_reg)
}
_ => Err("invalid delete target".into()),
},
@@ -1298,6 +1293,7 @@ impl Compiler {
Expression::ObjectExpression(obj) => {
let obj_reg = ctx.alloc_reg();
ctx.emit(opcode::encode(OpCode::NEW_OBJECT, obj_reg, 0, 0));
let prop_checkpoint = ctx.reg_checkpoint();
for prop in &obj.properties {
let oxide_parser::ObjectPropertyKind::ObjectProperty(p) = prop else {
return Err("spread properties not yet supported".into());
@@ -1321,6 +1317,7 @@ impl Compiler {
let idx = ctx.add_constant(Constant::String(prop_name.to_string()));
ctx.emit(opcode::encode(OpCode::DEFINE_ACCESSOR, obj_reg, get_reg, set_reg));
ctx.emit(idx as u32);
ctx.restore_reg_checkpoint(prop_checkpoint);
} else {
let idx = ctx.add_constant(Constant::String(prop_name.to_string()));
let key_reg = ctx.alloc_reg();
@@ -1334,6 +1331,7 @@ impl Compiler {
}
}
ctx.emit(opcode::encode(OpCode::SET_PROP, obj_reg, val_reg, key_reg));
ctx.restore_reg_checkpoint(prop_checkpoint);
}
}
Ok(obj_reg)
@@ -1342,6 +1340,7 @@ impl Compiler {
let arr_reg = ctx.alloc_reg();
let n = arr.elements.len() as u16;
ctx.emit(opcode::encode(OpCode::NEW_ARRAY, arr_reg, (n & 0xFF) as u8, ((n >> 8) & 0xFF) as u8));
let elem_checkpoint = ctx.reg_checkpoint();
for (i, elem) in arr.elements.iter().enumerate() {
let Some(e) = elem.as_expression() else {
return Err("spread not supported".into());
@@ -1351,6 +1350,7 @@ impl Compiler {
let idx = ctx.add_constant(Constant::Int(i as i32));
ctx.emit_load_const(idx_reg, idx);
ctx.emit(opcode::encode(OpCode::SET_ELEM, arr_reg, idx_reg, val_reg));
ctx.restore_reg_checkpoint(elem_checkpoint);
}
Ok(arr_reg)
}
@@ -1509,7 +1509,7 @@ impl Compiler {
_ => Err("member update not yet supported".into()),
},
Expression::Identifier(ident) => {
let var_reg = ctx.lookup(ident.name.as_str())?;
let var_reg = ctx.lookup_or_builtin(ident.name.as_str())?;
let r = ctx.alloc_reg();
ctx.emit(opcode::encode(OpCode::LOAD_VAR, r, var_reg, 0));
Ok(r)
@@ -101,6 +101,13 @@ impl SymbolTable {
Err(format!("Identifier '{name}' is not defined"))
}
pub fn lookup_any(&self, name: &str) -> Option<u8> {
self.scopes
.iter()
.rev()
.find_map(|scope| scope.bindings.get(name).map(|binding| binding.reg))
}
pub fn lookup_or_global(&mut self, name: &str, reg_for_new: u8) -> u8 {
for scope in self.scopes.iter().rev() {
if let Some(b) = scope.bindings.get(name) {
@@ -444,3 +444,20 @@ fn compile_class_expression_default_constructor() {
"expected at least one class constructor submodule"
);
}
#[test]
fn compile_large_pure_expression_reuses_temp_registers() {
let expr = std::iter::repeat("1").take(120).collect::<Vec<_>>().join(" + ");
let module = compile_source(&expr);
assert!(
module.n_registers < 16,
"pure expression should reuse temp registers, got {}",
module.n_registers
);
}
#[test]
fn compile_builtin_globals_are_registered_lazily() {
let module = compile_source("Object; Array; Math; JSON");
assert_eq!(module.builtin_reg_map.len(), 4, "only referenced builtins should allocate registers");
}
+19
View File
@@ -151,3 +151,22 @@ fn regression_delete_dynamic_property_throws_type_error() {
"vm error: uncaught TypeError: property deletion not supported"
);
}
#[test]
fn regression_large_expression_no_register_overflow() {
let expr = std::iter::repeat("1").take(120).collect::<Vec<_>>().join(" + ");
let source = format!("function f() {{ return {expr}; }} f()");
assert_eq!(eval(&source), "120");
}
#[test]
fn regression_many_declarations_keep_stable_registers() {
let decls = (0..120).map(|i| format!("var v{i} = {i};")).collect::<Vec<_>>().join("");
let source = format!("{decls} v0 + v57 + v119");
assert_eq!(eval(&source), "176");
}
#[test]
fn regression_method_call_receiver_survives_register_reuse() {
assert_eq!(eval("var o = { x: 41, f: function() { return this.x + 1; } }; o.f()"), "42");
}