diff --git a/crates/oxide_compiler/src/compiler.rs b/crates/oxide_compiler/src/compiler.rs index 5b193fe..e69e0eb 100644 --- a/crates/oxide_compiler/src/compiler.rs +++ b/crates/oxide_compiler/src/compiler.rs @@ -955,6 +955,268 @@ impl Compiler { Ok(()) } + /// Pre-register all builtin identifiers referenced anywhere in this body + /// (expressions, member objects, call args, class fields, etc.) so their + /// register slots are reserved *before* any temporary register is emitted. + /// + /// Without this, single-pass emission lazily allocates builtin slots via + /// `lookup_or_builtin` at first use, which can collide with a temporary + /// register already reused by `restore_reg_checkpoint`. Pre-scanning moves + /// builtin slot allocation ahead of the temporary register pool. + pub(crate) fn pre_register_builtin_references(&self, stmts: &[Statement], ctx: &mut CompileCtx) { + for stmt in stmts { + self.pre_scan_builtin_stmt(stmt, ctx); + } + } + + fn pre_scan_builtin_stmt(&self, stmt: &Statement, ctx: &mut CompileCtx) { + match stmt { + Statement::ExpressionStatement(es) => self.pre_scan_builtin_expr(&es.expression, ctx), + Statement::VariableDeclaration(vd) => { + for d in &vd.declarations { + if let Some(init) = &d.init { + self.pre_scan_builtin_expr(init, ctx); + } + } + } + Statement::ReturnStatement(rs) => { + if let Some(a) = &rs.argument { + self.pre_scan_builtin_expr(a, ctx); + } + } + Statement::IfStatement(is) => { + self.pre_scan_builtin_expr(&is.test, ctx); + self.pre_scan_builtin_stmt(&is.consequent, ctx); + if let Some(alt) = &is.alternate { + self.pre_scan_builtin_stmt(alt, ctx); + } + } + Statement::WhileStatement(wh) => { + self.pre_scan_builtin_expr(&wh.test, ctx); + self.pre_scan_builtin_stmt(&wh.body, ctx); + } + Statement::DoWhileStatement(dw) => { + self.pre_scan_builtin_stmt(&dw.body, ctx); + self.pre_scan_builtin_expr(&dw.test, ctx); + } + Statement::ForStatement(fs) => { + if let Some(init) = &fs.init { + if let Some(e) = init.as_expression() { + self.pre_scan_builtin_expr(e, ctx); + } else if let oxide_parser::ForStatementInit::VariableDeclaration(decl) = init { + for d in &decl.declarations { + if let Some(init_expr) = &d.init { + self.pre_scan_builtin_expr(init_expr, ctx); + } + } + } + } + if let Some(t) = &fs.test { + self.pre_scan_builtin_expr(t, ctx); + } + if let Some(u) = &fs.update { + self.pre_scan_builtin_expr(u, ctx); + } + self.pre_scan_builtin_stmt(&fs.body, ctx); + } + Statement::ForInStatement(fi) => { + self.pre_scan_builtin_expr(&fi.right, ctx); + self.pre_scan_builtin_stmt(&fi.body, ctx); + } + Statement::ForOfStatement(fo) => { + self.pre_scan_builtin_expr(&fo.right, ctx); + self.pre_scan_builtin_stmt(&fo.body, ctx); + } + Statement::SwitchStatement(sw) => { + self.pre_scan_builtin_expr(&sw.discriminant, ctx); + for case in &sw.cases { + if let Some(test) = &case.test { + self.pre_scan_builtin_expr(test, ctx); + } + for s in &case.consequent { + self.pre_scan_builtin_stmt(s, ctx); + } + } + } + Statement::ThrowStatement(ts) => { + self.pre_scan_builtin_expr(&ts.argument, ctx); + } + Statement::TryStatement(ts) => { + for s in &ts.block.body { + self.pre_scan_builtin_stmt(s, ctx); + } + if let Some(handler) = &ts.handler { + for s in &handler.body.body { + self.pre_scan_builtin_stmt(s, ctx); + } + } + if let Some(finalizer) = &ts.finalizer { + for s in &finalizer.body { + self.pre_scan_builtin_stmt(s, ctx); + } + } + } + Statement::BlockStatement(bs) => { + for s in &bs.body { + self.pre_scan_builtin_stmt(s, ctx); + } + } + Statement::LabeledStatement(ls) => self.pre_scan_builtin_stmt(&ls.body, ctx), + Statement::ClassDeclaration(cd) => { + if let Some(super_class) = &cd.super_class { + self.pre_scan_builtin_expr(super_class, ctx); + } + } + Statement::FunctionDeclaration(_) | Statement::BreakStatement(_) | Statement::ContinueStatement(_) => {} + _ => {} + } + } + + fn pre_scan_builtin_expr(&self, expr: &Expression, ctx: &mut CompileCtx) { + match expr { + Expression::Identifier(ident) => { + if CompileCtx::is_known_builtin(ident.name.as_str()) { + let _ = ctx.lookup_or_builtin(ident.name.as_str()); + } + } + Expression::BinaryExpression(bin) => { + self.pre_scan_builtin_expr(&bin.left, ctx); + self.pre_scan_builtin_expr(&bin.right, ctx); + } + Expression::UnaryExpression(un) => { + self.pre_scan_builtin_expr(&un.argument, ctx); + } + Expression::CallExpression(call) => { + self.pre_scan_builtin_expr(&call.callee, ctx); + for arg in &call.arguments { + if let Some(e) = arg.as_expression() { + self.pre_scan_builtin_expr(e, ctx); + } + } + } + Expression::NewExpression(ne) => { + self.pre_scan_builtin_expr(&ne.callee, ctx); + for arg in &ne.arguments { + if let Some(e) = arg.as_expression() { + self.pre_scan_builtin_expr(e, ctx); + } + } + } + Expression::LogicalExpression(log) => { + self.pre_scan_builtin_expr(&log.left, ctx); + self.pre_scan_builtin_expr(&log.right, ctx); + } + Expression::ConditionalExpression(cond) => { + self.pre_scan_builtin_expr(&cond.test, ctx); + self.pre_scan_builtin_expr(&cond.consequent, ctx); + self.pre_scan_builtin_expr(&cond.alternate, ctx); + } + Expression::PrivateInExpression(pin) => { + self.pre_scan_builtin_expr(&pin.right, ctx); + } + Expression::SequenceExpression(seq) => { + for e in &seq.expressions { + self.pre_scan_builtin_expr(e, ctx); + } + } + Expression::AssignmentExpression(assign) => { + if let Some(target) = assign.left.as_simple_assignment_target() { + self.pre_scan_builtin_target(target, ctx); + } + self.pre_scan_builtin_expr(&assign.right, ctx); + } + Expression::UpdateExpression(update) => { + self.pre_scan_builtin_target(&update.argument, ctx); + } + Expression::TemplateLiteral(tl) => { + for e in &tl.expressions { + self.pre_scan_builtin_expr(e, ctx); + } + } + Expression::TaggedTemplateExpression(tt) => { + self.pre_scan_builtin_expr(&tt.tag, ctx); + for e in &tt.quasi.expressions { + self.pre_scan_builtin_expr(e, ctx); + } + } + Expression::ObjectExpression(obj) => { + for prop in &obj.properties { + if let oxide_parser::ObjectPropertyKind::ObjectProperty(p) = prop { + if p.computed { + self.pre_scan_builtin_expr(p.key.to_expression(), ctx); + } + self.pre_scan_builtin_expr(&p.value, ctx); + } + } + } + Expression::ArrayExpression(arr) => { + for e in &arr.elements { + if let Some(e) = e.as_expression() { + self.pre_scan_builtin_expr(e, ctx); + } + } + } + Expression::PrivateFieldExpression(member) => { + self.pre_scan_builtin_expr(&member.object, ctx); + } + Expression::StaticMemberExpression(member) => { + self.pre_scan_builtin_expr(&member.object, ctx); + } + Expression::ComputedMemberExpression(member) => { + self.pre_scan_builtin_expr(&member.object, ctx); + self.pre_scan_builtin_expr(&member.expression, ctx); + } + Expression::ChainExpression(chain) => { + self.pre_scan_builtin_chain(&chain.expression, ctx); + } + Expression::ParenthesizedExpression(p) => self.pre_scan_builtin_expr(&p.expression, ctx), + Expression::ArrowFunctionExpression(_) + | Expression::FunctionExpression(_) + | Expression::ClassExpression(_) => {} + _ => {} + } + } + + fn pre_scan_builtin_target(&self, target: &oxide_parser::SimpleAssignmentTarget, ctx: &mut CompileCtx) { + match target { + oxide_parser::SimpleAssignmentTarget::StaticMemberExpression(member) => { + self.pre_scan_builtin_expr(&member.object, ctx); + } + oxide_parser::SimpleAssignmentTarget::ComputedMemberExpression(member) => { + self.pre_scan_builtin_expr(&member.object, ctx); + self.pre_scan_builtin_expr(&member.expression, ctx); + } + oxide_parser::SimpleAssignmentTarget::PrivateFieldExpression(member) => { + self.pre_scan_builtin_expr(&member.object, ctx); + } + _ => {} + } + } + + fn pre_scan_builtin_chain(&self, element: &oxide_parser::ChainElement, ctx: &mut CompileCtx) { + match element { + oxide_parser::ChainElement::StaticMemberExpression(member) => { + self.pre_scan_builtin_expr(&member.object, ctx); + } + oxide_parser::ChainElement::ComputedMemberExpression(member) => { + self.pre_scan_builtin_expr(&member.object, ctx); + self.pre_scan_builtin_expr(&member.expression, ctx); + } + oxide_parser::ChainElement::PrivateFieldExpression(member) => { + self.pre_scan_builtin_expr(&member.object, ctx); + } + oxide_parser::ChainElement::CallExpression(call) => { + self.pre_scan_builtin_expr(&call.callee, ctx); + for arg in &call.arguments { + if let Some(e) = arg.as_expression() { + self.pre_scan_builtin_expr(e, ctx); + } + } + } + _ => {} + } + } + fn pre_scan_expr(&self, expr: &Expression, parent_ctx: &mut CompileCtx) -> Result<(), String> { match expr { Expression::FunctionExpression(fe) => { @@ -1125,6 +1387,10 @@ impl Compiler { // Pre-scan: run analysis for nested function expressions to mark parent captures self.pre_scan_function_expressions(body_stmts, &mut ctx)?; + // Pre-register builtin identifier references before emitting any temporary + // register, so builtin slots never collide with reused temporaries. + self.pre_register_builtin_references(body_stmts, &mut ctx); + if let Some(emit) = emit_fields.as_mut() { if fields_after_super { let mut parent_bytecode = Vec::new(); @@ -1299,6 +1565,10 @@ impl Compiler { ctx.pre_register_builtins(); self.predeclare_function_declarations(&program.body, &mut ctx); + // Pre-register builtin identifier references before any temporary register + // is emitted, keeping builtin slots clear of the temporary register pool. + self.pre_register_builtin_references(&program.body, &mut ctx); + // First sub-pass: emit FunctionDeclarations (hoisting) // This ensures function objects are available before any code runs. for stmt in &program.body {