fix: 降低函数体寄存器压力
This commit is contained in:
@@ -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)
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user