we (web engine): Experimental web browser project to understand the limits of Claude
at object-model 1978 lines 69 kB view raw
1//! AST → register-based bytecode compiler. 2//! 3//! Walks the AST produced by the parser and emits bytecode instructions. 4//! Uses a simple greedy register allocator: each new temporary gets the next 5//! available register, and registers are freed when no longer needed. 6 7use crate::ast::*; 8use crate::bytecode::*; 9use crate::JsError; 10 11/// Compiler state for a single function scope. 12struct FunctionCompiler { 13 builder: BytecodeBuilder, 14 /// Maps local variable names to their register slots. 15 locals: Vec<Local>, 16 /// Next free register index. 17 next_reg: u8, 18 /// Stack of loop contexts for break/continue. 19 loop_stack: Vec<LoopCtx>, 20} 21 22#[derive(Debug, Clone)] 23struct Local { 24 name: String, 25 reg: Reg, 26} 27 28struct LoopCtx { 29 /// Label, if this is a labeled loop. 30 label: Option<String>, 31 /// Patch positions for break jumps. 32 break_patches: Vec<usize>, 33 /// Patch positions for continue jumps (patched after body compilation). 34 continue_patches: Vec<usize>, 35} 36 37impl FunctionCompiler { 38 fn new(name: String, param_count: u8) -> Self { 39 Self { 40 builder: BytecodeBuilder::new(name, param_count), 41 locals: Vec::new(), 42 next_reg: 0, 43 loop_stack: Vec::new(), 44 } 45 } 46 47 /// Allocate a register, updating the high-water mark. 48 fn alloc_reg(&mut self) -> Reg { 49 let r = self.next_reg; 50 self.next_reg = self.next_reg.checked_add(1).expect("register overflow"); 51 if self.next_reg > self.builder.func.register_count { 52 self.builder.func.register_count = self.next_reg; 53 } 54 r 55 } 56 57 /// Free the last allocated register (must be called in reverse order). 58 fn free_reg(&mut self, r: Reg) { 59 debug_assert_eq!( 60 r, 61 self.next_reg - 1, 62 "registers must be freed in reverse order" 63 ); 64 self.next_reg -= 1; 65 } 66 67 /// Look up a local variable by name. 68 fn find_local(&self, name: &str) -> Option<Reg> { 69 self.locals 70 .iter() 71 .rev() 72 .find(|l| l.name == name) 73 .map(|l| l.reg) 74 } 75 76 /// Define a local variable. 77 fn define_local(&mut self, name: &str) -> Reg { 78 let reg = self.alloc_reg(); 79 self.locals.push(Local { 80 name: name.to_string(), 81 reg, 82 }); 83 reg 84 } 85} 86 87/// Compile a parsed program into a top-level bytecode function. 88pub fn compile(program: &Program) -> Result<Function, JsError> { 89 let mut fc = FunctionCompiler::new("<main>".into(), 0); 90 91 // Reserve r0 for the implicit return value. 92 let result_reg = fc.alloc_reg(); 93 fc.builder.emit_reg(Op::LoadUndefined, result_reg); 94 95 compile_stmts(&mut fc, &program.body, result_reg)?; 96 97 fc.builder.emit_reg(Op::Return, result_reg); 98 Ok(fc.builder.finish()) 99} 100 101fn compile_stmts( 102 fc: &mut FunctionCompiler, 103 stmts: &[Stmt], 104 result_reg: Reg, 105) -> Result<(), JsError> { 106 for stmt in stmts { 107 compile_stmt(fc, stmt, result_reg)?; 108 } 109 Ok(()) 110} 111 112fn compile_stmt(fc: &mut FunctionCompiler, stmt: &Stmt, result_reg: Reg) -> Result<(), JsError> { 113 match &stmt.kind { 114 StmtKind::Expr(expr) => { 115 // Expression statement: compile and store result in result_reg. 116 compile_expr(fc, expr, result_reg)?; 117 } 118 119 StmtKind::Block(stmts) => { 120 let saved_locals = fc.locals.len(); 121 let saved_next = fc.next_reg; 122 compile_stmts(fc, stmts, result_reg)?; 123 // Pop locals from this block. 124 fc.locals.truncate(saved_locals); 125 fc.next_reg = saved_next; 126 } 127 128 StmtKind::VarDecl { 129 kind: _, 130 declarators, 131 } => { 132 for decl in declarators { 133 compile_var_declarator(fc, decl)?; 134 } 135 } 136 137 StmtKind::FunctionDecl(func_def) => { 138 compile_function_decl(fc, func_def)?; 139 } 140 141 StmtKind::If { 142 test, 143 consequent, 144 alternate, 145 } => { 146 compile_if(fc, test, consequent, alternate.as_deref(), result_reg)?; 147 } 148 149 StmtKind::While { test, body } => { 150 compile_while(fc, test, body, None, result_reg)?; 151 } 152 153 StmtKind::DoWhile { body, test } => { 154 compile_do_while(fc, body, test, None, result_reg)?; 155 } 156 157 StmtKind::For { 158 init, 159 test, 160 update, 161 body, 162 } => { 163 compile_for( 164 fc, 165 init.as_ref(), 166 test.as_ref(), 167 update.as_ref(), 168 body, 169 None, 170 result_reg, 171 )?; 172 } 173 174 StmtKind::ForIn { left, right, body } => { 175 let saved_locals = fc.locals.len(); 176 let saved_next = fc.next_reg; 177 178 // Evaluate the RHS object. 179 let obj_r = fc.alloc_reg(); 180 compile_expr(fc, right, obj_r)?; 181 182 // ForInInit: collect enumerable keys into an array. 183 let keys_r = fc.alloc_reg(); 184 fc.builder.emit_reg_reg(Op::ForInInit, keys_r, obj_r); 185 // obj_r is no longer needed but we don't free it (LIFO constraint). 186 187 // Index register (starts at 0). 188 let idx_r = fc.alloc_reg(); 189 fc.builder.emit_load_int8(idx_r, 0); 190 191 // Key and done registers (reused each iteration). 192 let key_r = fc.alloc_reg(); 193 let done_r = fc.alloc_reg(); 194 195 let loop_start = fc.builder.offset(); 196 197 // ForInNext: get next key or done flag. 198 fc.builder 199 .emit_reg4(Op::ForInNext, key_r, done_r, keys_r, idx_r); 200 201 // Exit loop if done. 202 let exit_patch = fc.builder.emit_cond_jump(Op::JumpIfTrue, done_r); 203 204 // Bind the loop variable. 205 match left { 206 ForInOfLeft::VarDecl { kind: _, pattern } => { 207 if let PatternKind::Identifier(name) = &pattern.kind { 208 let var_r = fc.define_local(name); 209 fc.builder.emit_reg_reg(Op::Move, var_r, key_r); 210 } 211 } 212 ForInOfLeft::Pattern(pattern) => { 213 if let PatternKind::Identifier(name) = &pattern.kind { 214 if let Some(local) = fc.find_local(name) { 215 fc.builder.emit_reg_reg(Op::Move, local, key_r); 216 } else { 217 let ni = fc.builder.add_name(name); 218 fc.builder.emit_store_global(ni, key_r); 219 } 220 } 221 } 222 } 223 224 // Compile the body. 225 fc.loop_stack.push(LoopCtx { 226 label: None, 227 break_patches: Vec::new(), 228 continue_patches: Vec::new(), 229 }); 230 compile_stmt(fc, body, result_reg)?; 231 232 // Increment index: idx = idx + 1. 233 // Use a temp register for the constant 1. Since we allocate it 234 // after the loop body, we can't free it with LIFO either — the 235 // saved_next restoration handles cleanup. 236 let one_r = fc.alloc_reg(); 237 fc.builder.emit_load_int8(one_r, 1); 238 fc.builder.emit_reg3(Op::Add, idx_r, idx_r, one_r); 239 240 // Jump back to loop start. 241 fc.builder.emit_jump_to(loop_start); 242 243 // Patch exit and continue jumps. 244 fc.builder.patch_jump(exit_patch); 245 let ctx = fc.loop_stack.pop().unwrap(); 246 for patch in ctx.break_patches { 247 fc.builder.patch_jump(patch); 248 } 249 for patch in ctx.continue_patches { 250 fc.builder.patch_jump_to(patch, loop_start); 251 } 252 253 // Restore locals/regs — frees all temporaries at once. 254 fc.locals.truncate(saved_locals); 255 fc.next_reg = saved_next; 256 } 257 258 StmtKind::ForOf { 259 left, 260 right, 261 body, 262 is_await: _, 263 } => { 264 let _ = left; 265 let tmp = fc.alloc_reg(); 266 compile_expr(fc, right, tmp)?; 267 fc.free_reg(tmp); 268 compile_stmt(fc, body, result_reg)?; 269 } 270 271 StmtKind::Return(expr) => { 272 let ret_reg = fc.alloc_reg(); 273 if let Some(e) = expr { 274 compile_expr(fc, e, ret_reg)?; 275 } else { 276 fc.builder.emit_reg(Op::LoadUndefined, ret_reg); 277 } 278 fc.builder.emit_reg(Op::Return, ret_reg); 279 fc.free_reg(ret_reg); 280 } 281 282 StmtKind::Throw(expr) => { 283 let tmp = fc.alloc_reg(); 284 compile_expr(fc, expr, tmp)?; 285 fc.builder.emit_reg(Op::Throw, tmp); 286 fc.free_reg(tmp); 287 } 288 289 StmtKind::Break(label) => { 290 // Find the matching loop context. 291 let idx = find_loop_ctx(&fc.loop_stack, label.as_deref()) 292 .ok_or_else(|| JsError::SyntaxError("break outside of loop".into()))?; 293 let patch = fc.builder.emit_jump(Op::Jump); 294 fc.loop_stack[idx].break_patches.push(patch); 295 } 296 297 StmtKind::Continue(label) => { 298 let idx = find_loop_ctx(&fc.loop_stack, label.as_deref()) 299 .ok_or_else(|| JsError::SyntaxError("continue outside of loop".into()))?; 300 let patch = fc.builder.emit_jump(Op::Jump); 301 fc.loop_stack[idx].continue_patches.push(patch); 302 } 303 304 StmtKind::Labeled { label, body } => { 305 // If body is a loop, propagate the label. 306 match &body.kind { 307 StmtKind::While { test, body: inner } => { 308 compile_while(fc, test, inner, Some(label.clone()), result_reg)?; 309 } 310 StmtKind::DoWhile { body: inner, test } => { 311 compile_do_while(fc, inner, test, Some(label.clone()), result_reg)?; 312 } 313 StmtKind::For { 314 init, 315 test, 316 update, 317 body: inner, 318 } => { 319 compile_for( 320 fc, 321 init.as_ref(), 322 test.as_ref(), 323 update.as_ref(), 324 inner, 325 Some(label.clone()), 326 result_reg, 327 )?; 328 } 329 _ => { 330 compile_stmt(fc, body, result_reg)?; 331 } 332 } 333 } 334 335 StmtKind::Switch { 336 discriminant, 337 cases, 338 } => { 339 compile_switch(fc, discriminant, cases, result_reg)?; 340 } 341 342 StmtKind::Try { 343 block, 344 handler, 345 finalizer, 346 } => { 347 // Simplified: compile blocks sequentially. 348 // Real try/catch needs exception table support from the VM. 349 compile_stmts(fc, block, result_reg)?; 350 if let Some(catch) = handler { 351 compile_stmts(fc, &catch.body, result_reg)?; 352 } 353 if let Some(fin) = finalizer { 354 compile_stmts(fc, fin, result_reg)?; 355 } 356 } 357 358 StmtKind::Empty | StmtKind::Debugger => { 359 // No-op. 360 } 361 362 StmtKind::With { object, body } => { 363 // Compile `with` as: evaluate object (discard), then run body. 364 // Proper `with` scope requires VM support. 365 let tmp = fc.alloc_reg(); 366 compile_expr(fc, object, tmp)?; 367 fc.free_reg(tmp); 368 compile_stmt(fc, body, result_reg)?; 369 } 370 371 StmtKind::Import { .. } => { 372 // Module imports are resolved before execution; no bytecode needed. 373 } 374 375 StmtKind::Export(export) => { 376 compile_export(fc, export, result_reg)?; 377 } 378 379 StmtKind::ClassDecl(class_def) => { 380 compile_class_decl(fc, class_def)?; 381 } 382 } 383 Ok(()) 384} 385 386// ── Variable declarations ─────────────────────────────────── 387 388fn compile_var_declarator(fc: &mut FunctionCompiler, decl: &VarDeclarator) -> Result<(), JsError> { 389 match &decl.pattern.kind { 390 PatternKind::Identifier(name) => { 391 let reg = fc.define_local(name); 392 if let Some(init) = &decl.init { 393 compile_expr(fc, init, reg)?; 394 } else { 395 fc.builder.emit_reg(Op::LoadUndefined, reg); 396 } 397 } 398 _ => { 399 // Destructuring: evaluate init, then bind patterns. 400 let tmp = fc.alloc_reg(); 401 if let Some(init) = &decl.init { 402 compile_expr(fc, init, tmp)?; 403 } else { 404 fc.builder.emit_reg(Op::LoadUndefined, tmp); 405 } 406 compile_destructuring_pattern(fc, &decl.pattern, tmp)?; 407 fc.free_reg(tmp); 408 } 409 } 410 Ok(()) 411} 412 413fn compile_destructuring_pattern( 414 fc: &mut FunctionCompiler, 415 pattern: &Pattern, 416 src: Reg, 417) -> Result<(), JsError> { 418 match &pattern.kind { 419 PatternKind::Identifier(name) => { 420 let reg = fc.define_local(name); 421 fc.builder.emit_reg_reg(Op::Move, reg, src); 422 } 423 PatternKind::Object { 424 properties, 425 rest: _, 426 } => { 427 for prop in properties { 428 let key_name = match &prop.key { 429 PropertyKey::Identifier(s) | PropertyKey::String(s) => s.clone(), 430 _ => { 431 return Err(JsError::SyntaxError( 432 "computed destructuring keys not yet supported".into(), 433 )); 434 } 435 }; 436 let val_reg = fc.alloc_reg(); 437 let name_idx = fc.builder.add_name(&key_name); 438 fc.builder.emit_get_prop_name(val_reg, src, name_idx); 439 compile_destructuring_pattern(fc, &prop.value, val_reg)?; 440 fc.free_reg(val_reg); 441 } 442 } 443 PatternKind::Array { elements, rest: _ } => { 444 for (i, elem) in elements.iter().enumerate() { 445 if let Some(pat) = elem { 446 let idx_reg = fc.alloc_reg(); 447 if i <= 127 { 448 fc.builder.emit_load_int8(idx_reg, i as i8); 449 } else { 450 let ci = fc.builder.add_constant(Constant::Number(i as f64)); 451 fc.builder.emit_reg_u16(Op::LoadConst, idx_reg, ci); 452 } 453 let val_reg = fc.alloc_reg(); 454 fc.builder.emit_reg3(Op::GetProperty, val_reg, src, idx_reg); 455 compile_destructuring_pattern(fc, pat, val_reg)?; 456 fc.free_reg(val_reg); 457 fc.free_reg(idx_reg); 458 } 459 } 460 } 461 PatternKind::Assign { left, right } => { 462 // Default value: if src is undefined, use default. 463 let val_reg = fc.alloc_reg(); 464 fc.builder.emit_reg_reg(Op::Move, val_reg, src); 465 // Check if undefined, if so use default. 466 let check_reg = fc.alloc_reg(); 467 let undef_reg = fc.alloc_reg(); 468 fc.builder.emit_reg(Op::LoadUndefined, undef_reg); 469 fc.builder 470 .emit_reg3(Op::StrictEq, check_reg, val_reg, undef_reg); 471 fc.free_reg(undef_reg); 472 let patch = fc.builder.emit_cond_jump(Op::JumpIfFalse, check_reg); 473 fc.free_reg(check_reg); 474 // Is undefined → evaluate default. 475 compile_expr(fc, right, val_reg)?; 476 fc.builder.patch_jump(patch); 477 compile_destructuring_pattern(fc, left, val_reg)?; 478 fc.free_reg(val_reg); 479 } 480 } 481 Ok(()) 482} 483 484// ── Function declarations ─────────────────────────────────── 485 486fn compile_function_decl(fc: &mut FunctionCompiler, func_def: &FunctionDef) -> Result<(), JsError> { 487 let name = func_def.id.clone().unwrap_or_default(); 488 let inner = compile_function_body(func_def)?; 489 let func_idx = fc.builder.add_function(inner); 490 491 let reg = fc.define_local(&name); 492 fc.builder.emit_reg_u16(Op::CreateClosure, reg, func_idx); 493 494 // Also store as global so inner/recursive calls via LoadGlobal can find it. 495 if !name.is_empty() { 496 let name_idx = fc.builder.add_name(&name); 497 fc.builder.emit_store_global(name_idx, reg); 498 } 499 Ok(()) 500} 501 502fn compile_function_body(func_def: &FunctionDef) -> Result<Function, JsError> { 503 let name = func_def.id.clone().unwrap_or_default(); 504 let param_count = func_def.params.len().min(255) as u8; 505 let mut inner = FunctionCompiler::new(name, param_count); 506 507 // Allocate registers for parameters. 508 for p in &func_def.params { 509 if let PatternKind::Identifier(name) = &p.kind { 510 inner.define_local(name); 511 } else { 512 // Destructuring param: allocate a register for the whole param, 513 // then destructure from it. 514 let _ = inner.alloc_reg(); 515 } 516 } 517 518 // Result register for the function body. 519 let result_reg = inner.alloc_reg(); 520 inner.builder.emit_reg(Op::LoadUndefined, result_reg); 521 522 compile_stmts(&mut inner, &func_def.body, result_reg)?; 523 524 // Implicit return undefined. 525 inner.builder.emit_reg(Op::Return, result_reg); 526 Ok(inner.builder.finish()) 527} 528 529// ── Class declarations ────────────────────────────────────── 530 531fn compile_class_decl(fc: &mut FunctionCompiler, class_def: &ClassDef) -> Result<(), JsError> { 532 let name = class_def.id.clone().unwrap_or_default(); 533 let reg = fc.define_local(&name); 534 535 // Find constructor or create empty one. 536 let ctor = class_def.body.iter().find(|m| { 537 matches!( 538 &m.kind, 539 ClassMemberKind::Method { 540 kind: MethodKind::Constructor, 541 .. 542 } 543 ) 544 }); 545 546 if let Some(member) = ctor { 547 if let ClassMemberKind::Method { value, .. } = &member.kind { 548 let inner = compile_function_body(value)?; 549 let func_idx = fc.builder.add_function(inner); 550 fc.builder.emit_reg_u16(Op::CreateClosure, reg, func_idx); 551 } 552 } else { 553 // No constructor: create a minimal function that returns undefined. 554 let mut empty = BytecodeBuilder::new(name.clone(), 0); 555 let r = 0u8; 556 empty.func.register_count = 1; 557 empty.emit_reg(Op::LoadUndefined, r); 558 empty.emit_reg(Op::Return, r); 559 let func_idx = fc.builder.add_function(empty.finish()); 560 fc.builder.emit_reg_u16(Op::CreateClosure, reg, func_idx); 561 } 562 563 // Compile methods: set them as properties on the constructor's prototype. 564 // This is simplified — real class compilation needs prototype chain setup. 565 for member in &class_def.body { 566 match &member.kind { 567 ClassMemberKind::Method { 568 key, 569 value, 570 kind, 571 is_static: _, 572 computed: _, 573 } => { 574 if matches!(kind, MethodKind::Constructor) { 575 continue; 576 } 577 let method_name = match key { 578 PropertyKey::Identifier(s) | PropertyKey::String(s) => s.clone(), 579 _ => continue, 580 }; 581 let inner = compile_function_body(value)?; 582 let func_idx = fc.builder.add_function(inner); 583 let method_reg = fc.alloc_reg(); 584 fc.builder 585 .emit_reg_u16(Op::CreateClosure, method_reg, func_idx); 586 let name_idx = fc.builder.add_name(&method_name); 587 fc.builder.emit_set_prop_name(reg, name_idx, method_reg); 588 fc.free_reg(method_reg); 589 } 590 ClassMemberKind::Property { .. } => { 591 // Class fields are set in constructor; skip here. 592 } 593 } 594 } 595 596 Ok(()) 597} 598 599// ── Export ─────────────────────────────────────────────────── 600 601fn compile_export( 602 fc: &mut FunctionCompiler, 603 export: &ExportDecl, 604 605 result_reg: Reg, 606) -> Result<(), JsError> { 607 match export { 608 ExportDecl::Declaration(stmt) => { 609 compile_stmt(fc, stmt, result_reg)?; 610 } 611 ExportDecl::Default(expr) => { 612 compile_expr(fc, expr, result_reg)?; 613 } 614 ExportDecl::Named { .. } | ExportDecl::AllFrom(_) => { 615 // Named re-exports are module-level; no bytecode needed. 616 } 617 } 618 Ok(()) 619} 620 621// ── Control flow ──────────────────────────────────────────── 622 623fn compile_if( 624 fc: &mut FunctionCompiler, 625 test: &Expr, 626 consequent: &Stmt, 627 alternate: Option<&Stmt>, 628 629 result_reg: Reg, 630) -> Result<(), JsError> { 631 let cond = fc.alloc_reg(); 632 compile_expr(fc, test, cond)?; 633 let else_patch = fc.builder.emit_cond_jump(Op::JumpIfFalse, cond); 634 fc.free_reg(cond); 635 636 compile_stmt(fc, consequent, result_reg)?; 637 638 if let Some(alt) = alternate { 639 let end_patch = fc.builder.emit_jump(Op::Jump); 640 fc.builder.patch_jump(else_patch); 641 compile_stmt(fc, alt, result_reg)?; 642 fc.builder.patch_jump(end_patch); 643 } else { 644 fc.builder.patch_jump(else_patch); 645 } 646 Ok(()) 647} 648 649fn compile_while( 650 fc: &mut FunctionCompiler, 651 test: &Expr, 652 body: &Stmt, 653 label: Option<String>, 654 655 result_reg: Reg, 656) -> Result<(), JsError> { 657 let loop_start = fc.builder.offset(); 658 659 let cond = fc.alloc_reg(); 660 compile_expr(fc, test, cond)?; 661 let exit_patch = fc.builder.emit_cond_jump(Op::JumpIfFalse, cond); 662 fc.free_reg(cond); 663 664 fc.loop_stack.push(LoopCtx { 665 label, 666 break_patches: Vec::new(), 667 continue_patches: Vec::new(), 668 }); 669 670 compile_stmt(fc, body, result_reg)?; 671 fc.builder.emit_jump_to(loop_start); 672 fc.builder.patch_jump(exit_patch); 673 674 let ctx = fc.loop_stack.pop().unwrap(); 675 for patch in ctx.break_patches { 676 fc.builder.patch_jump(patch); 677 } 678 // In a while loop, continue jumps back to the condition check (loop_start). 679 for patch in ctx.continue_patches { 680 fc.builder.patch_jump_to(patch, loop_start); 681 } 682 Ok(()) 683} 684 685fn compile_do_while( 686 fc: &mut FunctionCompiler, 687 body: &Stmt, 688 test: &Expr, 689 label: Option<String>, 690 691 result_reg: Reg, 692) -> Result<(), JsError> { 693 let loop_start = fc.builder.offset(); 694 695 fc.loop_stack.push(LoopCtx { 696 label, 697 break_patches: Vec::new(), 698 continue_patches: Vec::new(), 699 }); 700 701 compile_stmt(fc, body, result_reg)?; 702 703 // continue in do-while should jump here (the condition check). 704 let cond_start = fc.builder.offset(); 705 706 let cond = fc.alloc_reg(); 707 compile_expr(fc, test, cond)?; 708 fc.builder 709 .emit_cond_jump_to(Op::JumpIfTrue, cond, loop_start); 710 fc.free_reg(cond); 711 712 let ctx = fc.loop_stack.pop().unwrap(); 713 for patch in ctx.break_patches { 714 fc.builder.patch_jump(patch); 715 } 716 for patch in ctx.continue_patches { 717 fc.builder.patch_jump_to(patch, cond_start); 718 } 719 Ok(()) 720} 721 722fn compile_for( 723 fc: &mut FunctionCompiler, 724 init: Option<&ForInit>, 725 test: Option<&Expr>, 726 update: Option<&Expr>, 727 body: &Stmt, 728 label: Option<String>, 729 730 result_reg: Reg, 731) -> Result<(), JsError> { 732 let saved_locals = fc.locals.len(); 733 let saved_next = fc.next_reg; 734 735 // Init. 736 if let Some(init) = init { 737 match init { 738 ForInit::VarDecl { 739 kind: _, 740 declarators, 741 } => { 742 for decl in declarators { 743 compile_var_declarator(fc, decl)?; 744 } 745 } 746 ForInit::Expr(expr) => { 747 let tmp = fc.alloc_reg(); 748 compile_expr(fc, expr, tmp)?; 749 fc.free_reg(tmp); 750 } 751 } 752 } 753 754 let loop_start = fc.builder.offset(); 755 756 // Test. 757 let exit_patch = if let Some(test) = test { 758 let cond = fc.alloc_reg(); 759 compile_expr(fc, test, cond)?; 760 let patch = fc.builder.emit_cond_jump(Op::JumpIfFalse, cond); 761 fc.free_reg(cond); 762 Some(patch) 763 } else { 764 None 765 }; 766 767 fc.loop_stack.push(LoopCtx { 768 label, 769 break_patches: Vec::new(), 770 continue_patches: Vec::new(), 771 }); 772 773 compile_stmt(fc, body, result_reg)?; 774 775 // continue in a for-loop should jump here (the update expression). 776 let continue_target = fc.builder.offset(); 777 778 // Update. 779 if let Some(update) = update { 780 let tmp = fc.alloc_reg(); 781 compile_expr(fc, update, tmp)?; 782 fc.free_reg(tmp); 783 } 784 785 fc.builder.emit_jump_to(loop_start); 786 787 if let Some(patch) = exit_patch { 788 fc.builder.patch_jump(patch); 789 } 790 791 let ctx = fc.loop_stack.pop().unwrap(); 792 for patch in ctx.break_patches { 793 fc.builder.patch_jump(patch); 794 } 795 for patch in ctx.continue_patches { 796 fc.builder.patch_jump_to(patch, continue_target); 797 } 798 799 fc.locals.truncate(saved_locals); 800 fc.next_reg = saved_next; 801 Ok(()) 802} 803 804fn compile_switch( 805 fc: &mut FunctionCompiler, 806 discriminant: &Expr, 807 cases: &[SwitchCase], 808 809 result_reg: Reg, 810) -> Result<(), JsError> { 811 let disc_reg = fc.alloc_reg(); 812 compile_expr(fc, discriminant, disc_reg)?; 813 814 // Use a loop context for break statements. 815 fc.loop_stack.push(LoopCtx { 816 label: None, 817 break_patches: Vec::new(), 818 continue_patches: Vec::new(), 819 }); 820 821 // Phase 1: emit comparison jumps for each non-default case. 822 // Store (case_index, patch_position) for each case with a test. 823 let mut case_jump_patches: Vec<(usize, usize)> = Vec::new(); 824 let mut default_index: Option<usize> = None; 825 826 for (i, case) in cases.iter().enumerate() { 827 if let Some(test) = &case.test { 828 let test_reg = fc.alloc_reg(); 829 compile_expr(fc, test, test_reg)?; 830 let cmp_reg = fc.alloc_reg(); 831 fc.builder 832 .emit_reg3(Op::StrictEq, cmp_reg, disc_reg, test_reg); 833 let patch = fc.builder.emit_cond_jump(Op::JumpIfTrue, cmp_reg); 834 fc.free_reg(cmp_reg); 835 fc.free_reg(test_reg); 836 case_jump_patches.push((i, patch)); 837 } else { 838 default_index = Some(i); 839 } 840 } 841 842 // After all comparisons: jump to default body or end. 843 let fallthrough_patch = fc.builder.emit_jump(Op::Jump); 844 845 // Phase 2: emit case bodies in order (fall-through semantics). 846 let mut body_offsets: Vec<(usize, usize)> = Vec::new(); 847 for (i, case) in cases.iter().enumerate() { 848 body_offsets.push((i, fc.builder.offset())); 849 compile_stmts(fc, &case.consequent, result_reg)?; 850 } 851 852 let end_offset = fc.builder.offset(); 853 854 // Patch case test jumps to their respective body offsets. 855 for (case_idx, patch) in &case_jump_patches { 856 let body_offset = body_offsets 857 .iter() 858 .find(|(i, _)| i == case_idx) 859 .map(|(_, off)| *off) 860 .unwrap(); 861 fc.builder.patch_jump_to(*patch, body_offset); 862 } 863 864 // Patch fallthrough: jump to default body if present, otherwise to end. 865 if let Some(def_idx) = default_index { 866 let default_offset = body_offsets 867 .iter() 868 .find(|(i, _)| *i == def_idx) 869 .map(|(_, off)| *off) 870 .unwrap(); 871 fc.builder.patch_jump_to(fallthrough_patch, default_offset); 872 } else { 873 fc.builder.patch_jump_to(fallthrough_patch, end_offset); 874 } 875 876 fc.free_reg(disc_reg); 877 878 let ctx = fc.loop_stack.pop().unwrap(); 879 for patch in ctx.break_patches { 880 fc.builder.patch_jump(patch); 881 } 882 Ok(()) 883} 884 885fn find_loop_ctx(stack: &[LoopCtx], label: Option<&str>) -> Option<usize> { 886 if let Some(label) = label { 887 stack 888 .iter() 889 .rposition(|ctx| ctx.label.as_deref() == Some(label)) 890 } else { 891 if stack.is_empty() { 892 None 893 } else { 894 Some(stack.len() - 1) 895 } 896 } 897} 898 899// ── Expressions ───────────────────────────────────────────── 900 901fn compile_expr(fc: &mut FunctionCompiler, expr: &Expr, dst: Reg) -> Result<(), JsError> { 902 match &expr.kind { 903 ExprKind::Number(n) => { 904 // Optimize small integers. 905 let int_val = *n as i64; 906 if int_val as f64 == *n && (-128..=127).contains(&int_val) { 907 fc.builder.emit_load_int8(dst, int_val as i8); 908 } else { 909 let ci = fc.builder.add_constant(Constant::Number(*n)); 910 fc.builder.emit_reg_u16(Op::LoadConst, dst, ci); 911 } 912 } 913 914 ExprKind::String(s) => { 915 let ci = fc.builder.add_constant(Constant::String(s.clone())); 916 fc.builder.emit_reg_u16(Op::LoadConst, dst, ci); 917 } 918 919 ExprKind::Bool(true) => { 920 fc.builder.emit_reg(Op::LoadTrue, dst); 921 } 922 923 ExprKind::Bool(false) => { 924 fc.builder.emit_reg(Op::LoadFalse, dst); 925 } 926 927 ExprKind::Null => { 928 fc.builder.emit_reg(Op::LoadNull, dst); 929 } 930 931 ExprKind::Identifier(name) => { 932 if let Some(local_reg) = fc.find_local(name) { 933 if local_reg != dst { 934 fc.builder.emit_reg_reg(Op::Move, dst, local_reg); 935 } 936 } else { 937 // Global lookup. 938 let ni = fc.builder.add_name(name); 939 fc.builder.emit_load_global(dst, ni); 940 } 941 } 942 943 ExprKind::This => { 944 // `this` is loaded as a global named "this" (the VM binds it). 945 let ni = fc.builder.add_name("this"); 946 fc.builder.emit_load_global(dst, ni); 947 } 948 949 ExprKind::Binary { op, left, right } => { 950 let lhs = fc.alloc_reg(); 951 compile_expr(fc, left, lhs)?; 952 let rhs = fc.alloc_reg(); 953 compile_expr(fc, right, rhs)?; 954 let bytecode_op = binary_op_to_opcode(*op); 955 fc.builder.emit_reg3(bytecode_op, dst, lhs, rhs); 956 fc.free_reg(rhs); 957 fc.free_reg(lhs); 958 } 959 960 ExprKind::Unary { op, argument } => { 961 if *op == UnaryOp::Delete { 962 // Handle delete specially: need object + key form for member expressions. 963 match &argument.kind { 964 ExprKind::Member { 965 object, 966 property, 967 computed, 968 } => { 969 let obj_r = fc.alloc_reg(); 970 compile_expr(fc, object, obj_r)?; 971 let key_r = fc.alloc_reg(); 972 if *computed { 973 compile_expr(fc, property, key_r)?; 974 } else if let ExprKind::Identifier(name) = &property.kind { 975 let ci = fc.builder.add_constant(Constant::String(name.clone())); 976 fc.builder.emit_reg_u16(Op::LoadConst, key_r, ci); 977 } else { 978 compile_expr(fc, property, key_r)?; 979 } 980 fc.builder.emit_reg3(Op::Delete, dst, obj_r, key_r); 981 fc.free_reg(key_r); 982 fc.free_reg(obj_r); 983 } 984 _ => { 985 // `delete x` on a simple identifier: always true in non-strict mode. 986 fc.builder.emit_reg(Op::LoadTrue, dst); 987 } 988 } 989 } else { 990 let src = fc.alloc_reg(); 991 compile_expr(fc, argument, src)?; 992 match op { 993 UnaryOp::Minus => fc.builder.emit_reg_reg(Op::Neg, dst, src), 994 UnaryOp::Plus => { 995 fc.builder.emit_reg_reg(Op::Move, dst, src); 996 } 997 UnaryOp::Not => fc.builder.emit_reg_reg(Op::LogicalNot, dst, src), 998 UnaryOp::BitwiseNot => fc.builder.emit_reg_reg(Op::BitNot, dst, src), 999 UnaryOp::Typeof => fc.builder.emit_reg_reg(Op::TypeOf, dst, src), 1000 UnaryOp::Void => fc.builder.emit_reg_reg(Op::Void, dst, src), 1001 UnaryOp::Delete => unreachable!(), 1002 } 1003 fc.free_reg(src); 1004 } 1005 } 1006 1007 ExprKind::Update { 1008 op, 1009 argument, 1010 prefix, 1011 } => { 1012 // Get current value. 1013 compile_expr(fc, argument, dst)?; 1014 1015 let one = fc.alloc_reg(); 1016 fc.builder.emit_load_int8(one, 1); 1017 1018 if *prefix { 1019 // ++x / --x: modify first, return modified. 1020 match op { 1021 UpdateOp::Increment => fc.builder.emit_reg3(Op::Add, dst, dst, one), 1022 UpdateOp::Decrement => fc.builder.emit_reg3(Op::Sub, dst, dst, one), 1023 } 1024 // Store back. 1025 compile_store(fc, argument, dst)?; 1026 } else { 1027 // x++ / x--: return original, then modify. 1028 let tmp = fc.alloc_reg(); 1029 fc.builder.emit_reg_reg(Op::Move, tmp, dst); 1030 match op { 1031 UpdateOp::Increment => fc.builder.emit_reg3(Op::Add, tmp, tmp, one), 1032 UpdateOp::Decrement => fc.builder.emit_reg3(Op::Sub, tmp, tmp, one), 1033 } 1034 compile_store(fc, argument, tmp)?; 1035 fc.free_reg(tmp); 1036 } 1037 fc.free_reg(one); 1038 } 1039 1040 ExprKind::Logical { op, left, right } => { 1041 compile_expr(fc, left, dst)?; 1042 match op { 1043 LogicalOp::And => { 1044 // Short-circuit: if falsy, skip right. 1045 let skip = fc.builder.emit_cond_jump(Op::JumpIfFalse, dst); 1046 compile_expr(fc, right, dst)?; 1047 fc.builder.patch_jump(skip); 1048 } 1049 LogicalOp::Or => { 1050 let skip = fc.builder.emit_cond_jump(Op::JumpIfTrue, dst); 1051 compile_expr(fc, right, dst)?; 1052 fc.builder.patch_jump(skip); 1053 } 1054 LogicalOp::Nullish => { 1055 let skip = fc.builder.emit_cond_jump(Op::JumpIfNullish, dst); 1056 // If NOT nullish, skip the right side. Wait — JumpIfNullish 1057 // should mean "jump if nullish" so we want: evaluate left, 1058 // if NOT nullish skip right. 1059 // Let's invert: evaluate left, check if nullish → evaluate right. 1060 // We need the jump to skip the "evaluate right" if NOT nullish. 1061 // Since JumpIfNullish jumps when nullish, we need the inverse. 1062 // Instead: use a two-step approach. 1063 // 1064 // Actually, rethink: for `a ?? b`: 1065 // 1. evaluate a → dst 1066 // 2. if dst is NOT null/undefined, jump to end 1067 // 3. evaluate b → dst 1068 // end: 1069 // JumpIfNullish jumps when IS nullish. So we want jump when NOT nullish. 1070 // Let's just use a "not nullish" check. 1071 // For now: negate and use JumpIfFalse. 1072 // Actually simpler: skip right when not nullish. 1073 // JumpIfNullish jumps WHEN nullish. We want to jump over right when NOT nullish. 1074 // So: 1075 // evaluate a → dst 1076 // JumpIfNullish dst → evaluate_right 1077 // Jump → end 1078 // evaluate_right: evaluate b → dst 1079 // end: 1080 // But we already emitted JumpIfNullish. Let's fix this. 1081 // The JumpIfNullish we emitted jumps to "after patch", which is where 1082 // we'll put the right-side code. We need another jump to skip right. 1083 let end_patch = fc.builder.emit_jump(Op::Jump); 1084 fc.builder.patch_jump(skip); // nullish → evaluate right 1085 compile_expr(fc, right, dst)?; 1086 fc.builder.patch_jump(end_patch); 1087 } 1088 } 1089 } 1090 1091 ExprKind::Assignment { op, left, right } => { 1092 if *op == AssignOp::Assign { 1093 compile_expr(fc, right, dst)?; 1094 compile_store(fc, left, dst)?; 1095 } else { 1096 // Compound assignment: load current, operate, store. 1097 compile_expr(fc, left, dst)?; 1098 let rhs = fc.alloc_reg(); 1099 compile_expr(fc, right, rhs)?; 1100 let arith_op = compound_assign_op(*op); 1101 fc.builder.emit_reg3(arith_op, dst, dst, rhs); 1102 fc.free_reg(rhs); 1103 compile_store(fc, left, dst)?; 1104 } 1105 } 1106 1107 ExprKind::Conditional { 1108 test, 1109 consequent, 1110 alternate, 1111 } => { 1112 let cond = fc.alloc_reg(); 1113 compile_expr(fc, test, cond)?; 1114 let else_patch = fc.builder.emit_cond_jump(Op::JumpIfFalse, cond); 1115 fc.free_reg(cond); 1116 compile_expr(fc, consequent, dst)?; 1117 let end_patch = fc.builder.emit_jump(Op::Jump); 1118 fc.builder.patch_jump(else_patch); 1119 compile_expr(fc, alternate, dst)?; 1120 fc.builder.patch_jump(end_patch); 1121 } 1122 1123 ExprKind::Call { callee, arguments } => { 1124 let func_reg = fc.alloc_reg(); 1125 compile_expr(fc, callee, func_reg)?; 1126 1127 let args_start = fc.next_reg; 1128 let arg_count = arguments.len().min(255) as u8; 1129 for arg in arguments { 1130 let arg_reg = fc.alloc_reg(); 1131 compile_expr(fc, arg, arg_reg)?; 1132 } 1133 1134 fc.builder.emit_call(dst, func_reg, args_start, arg_count); 1135 1136 // Free argument registers (in reverse). 1137 for _ in 0..arg_count { 1138 fc.next_reg -= 1; 1139 } 1140 fc.free_reg(func_reg); 1141 } 1142 1143 ExprKind::New { callee, arguments } => { 1144 // For now, compile like a regular call. The VM will differentiate 1145 // based on the `New` vs `Call` distinction (TODO: add NewCall opcode). 1146 let func_reg = fc.alloc_reg(); 1147 compile_expr(fc, callee, func_reg)?; 1148 1149 let args_start = fc.next_reg; 1150 let arg_count = arguments.len().min(255) as u8; 1151 for arg in arguments { 1152 let arg_reg = fc.alloc_reg(); 1153 compile_expr(fc, arg, arg_reg)?; 1154 } 1155 1156 fc.builder.emit_call(dst, func_reg, args_start, arg_count); 1157 1158 for _ in 0..arg_count { 1159 fc.next_reg -= 1; 1160 } 1161 fc.free_reg(func_reg); 1162 } 1163 1164 ExprKind::Member { 1165 object, 1166 property, 1167 computed, 1168 } => { 1169 let obj_reg = fc.alloc_reg(); 1170 compile_expr(fc, object, obj_reg)?; 1171 1172 if !computed { 1173 // Static member: obj.prop → GetPropertyByName. 1174 if let ExprKind::Identifier(name) = &property.kind { 1175 let ni = fc.builder.add_name(name); 1176 fc.builder.emit_get_prop_name(dst, obj_reg, ni); 1177 } else { 1178 let key_reg = fc.alloc_reg(); 1179 compile_expr(fc, property, key_reg)?; 1180 fc.builder.emit_reg3(Op::GetProperty, dst, obj_reg, key_reg); 1181 fc.free_reg(key_reg); 1182 } 1183 } else { 1184 // Computed member: obj[expr]. 1185 let key_reg = fc.alloc_reg(); 1186 compile_expr(fc, property, key_reg)?; 1187 fc.builder.emit_reg3(Op::GetProperty, dst, obj_reg, key_reg); 1188 fc.free_reg(key_reg); 1189 } 1190 fc.free_reg(obj_reg); 1191 } 1192 1193 ExprKind::Array(elements) => { 1194 fc.builder.emit_reg(Op::CreateArray, dst); 1195 for (i, elem) in elements.iter().enumerate() { 1196 if let Some(el) = elem { 1197 let val_reg = fc.alloc_reg(); 1198 match el { 1199 ArrayElement::Expr(e) => compile_expr(fc, e, val_reg)?, 1200 ArrayElement::Spread(e) => { 1201 // Spread in array: simplified, just compile the expression. 1202 compile_expr(fc, e, val_reg)?; 1203 } 1204 } 1205 let idx_reg = fc.alloc_reg(); 1206 if i <= 127 { 1207 fc.builder.emit_load_int8(idx_reg, i as i8); 1208 } else { 1209 let ci = fc.builder.add_constant(Constant::Number(i as f64)); 1210 fc.builder.emit_reg_u16(Op::LoadConst, idx_reg, ci); 1211 } 1212 fc.builder.emit_reg3(Op::SetProperty, dst, idx_reg, val_reg); 1213 fc.free_reg(idx_reg); 1214 fc.free_reg(val_reg); 1215 } 1216 } 1217 } 1218 1219 ExprKind::Object(properties) => { 1220 fc.builder.emit_reg(Op::CreateObject, dst); 1221 for prop in properties { 1222 let val_reg = fc.alloc_reg(); 1223 if let Some(value) = &prop.value { 1224 compile_expr(fc, value, val_reg)?; 1225 } else { 1226 // Shorthand: `{ x }` means `{ x: x }`. 1227 if let PropertyKey::Identifier(name) = &prop.key { 1228 if let Some(local) = fc.find_local(name) { 1229 fc.builder.emit_reg_reg(Op::Move, val_reg, local); 1230 } else { 1231 let ni = fc.builder.add_name(name); 1232 fc.builder.emit_load_global(val_reg, ni); 1233 } 1234 } else { 1235 fc.builder.emit_reg(Op::LoadUndefined, val_reg); 1236 } 1237 } 1238 1239 match &prop.key { 1240 PropertyKey::Identifier(name) | PropertyKey::String(name) => { 1241 let ni = fc.builder.add_name(name); 1242 fc.builder.emit_set_prop_name(dst, ni, val_reg); 1243 } 1244 PropertyKey::Number(n) => { 1245 let key_reg = fc.alloc_reg(); 1246 let ci = fc.builder.add_constant(Constant::Number(*n)); 1247 fc.builder.emit_reg_u16(Op::LoadConst, key_reg, ci); 1248 fc.builder.emit_reg3(Op::SetProperty, dst, key_reg, val_reg); 1249 fc.free_reg(key_reg); 1250 } 1251 PropertyKey::Computed(expr) => { 1252 let key_reg = fc.alloc_reg(); 1253 compile_expr(fc, expr, key_reg)?; 1254 fc.builder.emit_reg3(Op::SetProperty, dst, key_reg, val_reg); 1255 fc.free_reg(key_reg); 1256 } 1257 } 1258 fc.free_reg(val_reg); 1259 } 1260 } 1261 1262 ExprKind::Function(func_def) => { 1263 let inner = compile_function_body(func_def)?; 1264 let func_idx = fc.builder.add_function(inner); 1265 fc.builder.emit_reg_u16(Op::CreateClosure, dst, func_idx); 1266 } 1267 1268 ExprKind::Arrow { 1269 params, 1270 body, 1271 is_async: _, 1272 } => { 1273 let param_count = params.len().min(255) as u8; 1274 let mut inner = FunctionCompiler::new("<arrow>".into(), param_count); 1275 for p in params { 1276 if let PatternKind::Identifier(name) = &p.kind { 1277 inner.define_local(name); 1278 } else { 1279 let _ = inner.alloc_reg(); 1280 } 1281 } 1282 let result = inner.alloc_reg(); 1283 match body { 1284 ArrowBody::Expr(e) => { 1285 compile_expr(&mut inner, e, result)?; 1286 } 1287 ArrowBody::Block(stmts) => { 1288 inner.builder.emit_reg(Op::LoadUndefined, result); 1289 compile_stmts(&mut inner, stmts, result)?; 1290 } 1291 } 1292 inner.builder.emit_reg(Op::Return, result); 1293 let inner_func = inner.builder.finish(); 1294 let func_idx = fc.builder.add_function(inner_func); 1295 fc.builder.emit_reg_u16(Op::CreateClosure, dst, func_idx); 1296 } 1297 1298 ExprKind::Class(class_def) => { 1299 // Class expression: compile like class decl but into dst. 1300 let name = class_def.id.clone().unwrap_or_default(); 1301 // Find constructor. 1302 let ctor = class_def.body.iter().find(|m| { 1303 matches!( 1304 &m.kind, 1305 ClassMemberKind::Method { 1306 kind: MethodKind::Constructor, 1307 .. 1308 } 1309 ) 1310 }); 1311 if let Some(member) = ctor { 1312 if let ClassMemberKind::Method { value, .. } = &member.kind { 1313 let inner = compile_function_body(value)?; 1314 let func_idx = fc.builder.add_function(inner); 1315 fc.builder.emit_reg_u16(Op::CreateClosure, dst, func_idx); 1316 } 1317 } else { 1318 let mut empty = BytecodeBuilder::new(name, 0); 1319 let r = 0u8; 1320 empty.func.register_count = 1; 1321 empty.emit_reg(Op::LoadUndefined, r); 1322 empty.emit_reg(Op::Return, r); 1323 let func_idx = fc.builder.add_function(empty.finish()); 1324 fc.builder.emit_reg_u16(Op::CreateClosure, dst, func_idx); 1325 } 1326 1327 // Compile methods as properties on the constructor. 1328 for member in &class_def.body { 1329 match &member.kind { 1330 ClassMemberKind::Method { 1331 key, 1332 value, 1333 kind, 1334 is_static: _, 1335 computed: _, 1336 } => { 1337 if matches!(kind, MethodKind::Constructor) { 1338 continue; 1339 } 1340 let method_name = match key { 1341 PropertyKey::Identifier(s) | PropertyKey::String(s) => s.clone(), 1342 _ => continue, 1343 }; 1344 let inner = compile_function_body(value)?; 1345 let func_idx = fc.builder.add_function(inner); 1346 let method_reg = fc.alloc_reg(); 1347 fc.builder 1348 .emit_reg_u16(Op::CreateClosure, method_reg, func_idx); 1349 let name_idx = fc.builder.add_name(&method_name); 1350 fc.builder.emit_set_prop_name(dst, name_idx, method_reg); 1351 fc.free_reg(method_reg); 1352 } 1353 ClassMemberKind::Property { .. } => {} 1354 } 1355 } 1356 } 1357 1358 ExprKind::Sequence(exprs) => { 1359 for e in exprs { 1360 compile_expr(fc, e, dst)?; 1361 } 1362 } 1363 1364 ExprKind::Spread(inner) => { 1365 compile_expr(fc, inner, dst)?; 1366 } 1367 1368 ExprKind::TemplateLiteral { 1369 quasis, 1370 expressions, 1371 } => { 1372 // Compile template literal as string concatenation. 1373 if quasis.len() == 1 && expressions.is_empty() { 1374 let ci = fc.builder.add_constant(Constant::String(quasis[0].clone())); 1375 fc.builder.emit_reg_u16(Op::LoadConst, dst, ci); 1376 } else { 1377 // Start with first quasi. 1378 let ci = fc.builder.add_constant(Constant::String(quasis[0].clone())); 1379 fc.builder.emit_reg_u16(Op::LoadConst, dst, ci); 1380 for (i, expr) in expressions.iter().enumerate() { 1381 let tmp = fc.alloc_reg(); 1382 compile_expr(fc, expr, tmp)?; 1383 fc.builder.emit_reg3(Op::Add, dst, dst, tmp); 1384 fc.free_reg(tmp); 1385 if i + 1 < quasis.len() { 1386 let qi = fc 1387 .builder 1388 .add_constant(Constant::String(quasis[i + 1].clone())); 1389 let tmp2 = fc.alloc_reg(); 1390 fc.builder.emit_reg_u16(Op::LoadConst, tmp2, qi); 1391 fc.builder.emit_reg3(Op::Add, dst, dst, tmp2); 1392 fc.free_reg(tmp2); 1393 } 1394 } 1395 } 1396 } 1397 1398 ExprKind::TaggedTemplate { tag, quasi } => { 1399 // Simplified: call tag with the template as argument. 1400 let func_reg = fc.alloc_reg(); 1401 compile_expr(fc, tag, func_reg)?; 1402 let arg_reg = fc.alloc_reg(); 1403 compile_expr(fc, quasi, arg_reg)?; 1404 fc.builder.emit_call(dst, func_reg, arg_reg, 1); 1405 fc.free_reg(arg_reg); 1406 fc.free_reg(func_reg); 1407 } 1408 1409 ExprKind::Yield { 1410 argument, 1411 delegate: _, 1412 } => { 1413 // Yield is a VM-level operation; for now compile the argument. 1414 if let Some(arg) = argument { 1415 compile_expr(fc, arg, dst)?; 1416 } else { 1417 fc.builder.emit_reg(Op::LoadUndefined, dst); 1418 } 1419 } 1420 1421 ExprKind::Await(inner) => { 1422 // Await is a VM-level operation; compile the argument. 1423 compile_expr(fc, inner, dst)?; 1424 } 1425 1426 ExprKind::RegExp { .. } => { 1427 // RegExp literals are created at runtime by the VM. 1428 fc.builder.emit_reg(Op::LoadUndefined, dst); 1429 } 1430 1431 ExprKind::OptionalChain { base } => { 1432 compile_expr(fc, base, dst)?; 1433 } 1434 } 1435 Ok(()) 1436} 1437 1438/// Compile a store operation (assignment target). 1439fn compile_store(fc: &mut FunctionCompiler, target: &Expr, src: Reg) -> Result<(), JsError> { 1440 match &target.kind { 1441 ExprKind::Identifier(name) => { 1442 if let Some(local) = fc.find_local(name) { 1443 if local != src { 1444 fc.builder.emit_reg_reg(Op::Move, local, src); 1445 } 1446 } else { 1447 let ni = fc.builder.add_name(name); 1448 fc.builder.emit_store_global(ni, src); 1449 } 1450 } 1451 ExprKind::Member { 1452 object, 1453 property, 1454 computed, 1455 } => { 1456 let obj_reg = fc.alloc_reg(); 1457 compile_expr(fc, object, obj_reg)?; 1458 if !computed { 1459 if let ExprKind::Identifier(name) = &property.kind { 1460 let ni = fc.builder.add_name(name); 1461 fc.builder.emit_set_prop_name(obj_reg, ni, src); 1462 } else { 1463 let key_reg = fc.alloc_reg(); 1464 compile_expr(fc, property, key_reg)?; 1465 fc.builder.emit_reg3(Op::SetProperty, obj_reg, key_reg, src); 1466 fc.free_reg(key_reg); 1467 } 1468 } else { 1469 let key_reg = fc.alloc_reg(); 1470 compile_expr(fc, property, key_reg)?; 1471 fc.builder.emit_reg3(Op::SetProperty, obj_reg, key_reg, src); 1472 fc.free_reg(key_reg); 1473 } 1474 fc.free_reg(obj_reg); 1475 } 1476 _ => { 1477 // Other assignment targets (destructuring) not handled here. 1478 } 1479 } 1480 Ok(()) 1481} 1482 1483fn binary_op_to_opcode(op: BinaryOp) -> Op { 1484 match op { 1485 BinaryOp::Add => Op::Add, 1486 BinaryOp::Sub => Op::Sub, 1487 BinaryOp::Mul => Op::Mul, 1488 BinaryOp::Div => Op::Div, 1489 BinaryOp::Rem => Op::Rem, 1490 BinaryOp::Exp => Op::Exp, 1491 BinaryOp::Eq => Op::Eq, 1492 BinaryOp::Ne => Op::NotEq, 1493 BinaryOp::StrictEq => Op::StrictEq, 1494 BinaryOp::StrictNe => Op::StrictNotEq, 1495 BinaryOp::Lt => Op::LessThan, 1496 BinaryOp::Le => Op::LessEq, 1497 BinaryOp::Gt => Op::GreaterThan, 1498 BinaryOp::Ge => Op::GreaterEq, 1499 BinaryOp::Shl => Op::ShiftLeft, 1500 BinaryOp::Shr => Op::ShiftRight, 1501 BinaryOp::Ushr => Op::UShiftRight, 1502 BinaryOp::BitAnd => Op::BitAnd, 1503 BinaryOp::BitOr => Op::BitOr, 1504 BinaryOp::BitXor => Op::BitXor, 1505 BinaryOp::In => Op::In, 1506 BinaryOp::Instanceof => Op::InstanceOf, 1507 } 1508} 1509 1510fn compound_assign_op(op: AssignOp) -> Op { 1511 match op { 1512 AssignOp::AddAssign => Op::Add, 1513 AssignOp::SubAssign => Op::Sub, 1514 AssignOp::MulAssign => Op::Mul, 1515 AssignOp::DivAssign => Op::Div, 1516 AssignOp::RemAssign => Op::Rem, 1517 AssignOp::ExpAssign => Op::Exp, 1518 AssignOp::ShlAssign => Op::ShiftLeft, 1519 AssignOp::ShrAssign => Op::ShiftRight, 1520 AssignOp::UshrAssign => Op::UShiftRight, 1521 AssignOp::BitAndAssign => Op::BitAnd, 1522 AssignOp::BitOrAssign => Op::BitOr, 1523 AssignOp::BitXorAssign => Op::BitXor, 1524 AssignOp::AndAssign => Op::BitAnd, // logical AND assignment uses short-circuit; simplified here 1525 AssignOp::OrAssign => Op::BitOr, // likewise 1526 AssignOp::NullishAssign => Op::Move, // simplified 1527 AssignOp::Assign => unreachable!(), 1528 } 1529} 1530 1531#[cfg(test)] 1532mod tests { 1533 use super::*; 1534 use crate::parser::Parser; 1535 1536 /// Helper: parse and compile source, return the top-level function. 1537 fn compile_src(src: &str) -> Function { 1538 let program = Parser::parse(src).expect("parse failed"); 1539 compile(&program).expect("compile failed") 1540 } 1541 1542 #[test] 1543 fn test_compile_number_literal() { 1544 let f = compile_src("42;"); 1545 let dis = f.disassemble(); 1546 assert!(dis.contains("LoadInt8 r0, 42"), "got:\n{dis}"); 1547 assert!(dis.contains("Return r0")); 1548 } 1549 1550 #[test] 1551 fn test_compile_large_number() { 1552 let f = compile_src("3.14;"); 1553 let dis = f.disassemble(); 1554 assert!(dis.contains("LoadConst r0, #0"), "got:\n{dis}"); 1555 assert!( 1556 f.constants.contains(&Constant::Number(3.14)), 1557 "constants: {:?}", 1558 f.constants 1559 ); 1560 } 1561 1562 #[test] 1563 fn test_compile_string() { 1564 let f = compile_src("\"hello\";"); 1565 let dis = f.disassemble(); 1566 assert!(dis.contains("LoadConst r0, #0")); 1567 assert!(f.constants.contains(&Constant::String("hello".into()))); 1568 } 1569 1570 #[test] 1571 fn test_compile_bool_null() { 1572 let f = compile_src("true; false; null;"); 1573 let dis = f.disassemble(); 1574 assert!(dis.contains("LoadTrue r0")); 1575 assert!(dis.contains("LoadFalse r0")); 1576 assert!(dis.contains("LoadNull r0")); 1577 } 1578 1579 #[test] 1580 fn test_compile_binary_arithmetic() { 1581 let f = compile_src("1 + 2;"); 1582 let dis = f.disassemble(); 1583 assert!(dis.contains("Add r0, r1, r2"), "got:\n{dis}"); 1584 } 1585 1586 #[test] 1587 fn test_compile_nested_arithmetic() { 1588 let f = compile_src("(1 + 2) * 3;"); 1589 let dis = f.disassemble(); 1590 assert!(dis.contains("Add"), "got:\n{dis}"); 1591 assert!(dis.contains("Mul"), "got:\n{dis}"); 1592 } 1593 1594 #[test] 1595 fn test_compile_var_decl() { 1596 let f = compile_src("var x = 10; x;"); 1597 let dis = f.disassemble(); 1598 // x should get a register, then be loaded from that register. 1599 assert!(dis.contains("LoadInt8"), "got:\n{dis}"); 1600 assert!( 1601 dis.contains("Move") || dis.contains("LoadInt8"), 1602 "got:\n{dis}" 1603 ); 1604 } 1605 1606 #[test] 1607 fn test_compile_let_const() { 1608 let f = compile_src("let a = 1; const b = 2; a + b;"); 1609 let dis = f.disassemble(); 1610 assert!(dis.contains("Add"), "got:\n{dis}"); 1611 } 1612 1613 #[test] 1614 fn test_compile_if_else() { 1615 let f = compile_src("if (true) { 1; } else { 2; }"); 1616 let dis = f.disassemble(); 1617 assert!(dis.contains("JumpIfFalse"), "got:\n{dis}"); 1618 assert!(dis.contains("Jump"), "got:\n{dis}"); 1619 } 1620 1621 #[test] 1622 fn test_compile_while() { 1623 let f = compile_src("var i = 0; while (i < 10) { i = i + 1; }"); 1624 let dis = f.disassemble(); 1625 assert!(dis.contains("LessThan"), "got:\n{dis}"); 1626 assert!(dis.contains("JumpIfFalse"), "got:\n{dis}"); 1627 assert!( 1628 dis.contains("Jump"), 1629 "backward jump should be present: {dis}" 1630 ); 1631 } 1632 1633 #[test] 1634 fn test_compile_do_while() { 1635 let f = compile_src("var i = 0; do { i = i + 1; } while (i < 5);"); 1636 let dis = f.disassemble(); 1637 assert!(dis.contains("JumpIfTrue"), "got:\n{dis}"); 1638 } 1639 1640 #[test] 1641 fn test_compile_for_loop() { 1642 let f = compile_src("for (var i = 0; i < 10; i = i + 1) { i; }"); 1643 let dis = f.disassemble(); 1644 assert!(dis.contains("LessThan"), "got:\n{dis}"); 1645 assert!(dis.contains("JumpIfFalse"), "got:\n{dis}"); 1646 } 1647 1648 #[test] 1649 fn test_compile_function_decl() { 1650 let f = compile_src("function add(a, b) { return a + b; }"); 1651 let dis = f.disassemble(); 1652 assert!(dis.contains("CreateClosure"), "got:\n{dis}"); 1653 assert!(!f.functions.is_empty(), "should have nested function"); 1654 let inner = &f.functions[0]; 1655 assert_eq!(inner.name, "add"); 1656 assert_eq!(inner.param_count, 2); 1657 let inner_dis = inner.disassemble(); 1658 assert!(inner_dis.contains("Add"), "inner:\n{inner_dis}"); 1659 assert!(inner_dis.contains("Return"), "inner:\n{inner_dis}"); 1660 } 1661 1662 #[test] 1663 fn test_compile_function_call() { 1664 let f = compile_src("function f() { return 42; } f();"); 1665 let dis = f.disassemble(); 1666 assert!(dis.contains("Call"), "got:\n{dis}"); 1667 } 1668 1669 #[test] 1670 fn test_compile_arrow_function() { 1671 let f = compile_src("var add = (a, b) => a + b;"); 1672 let dis = f.disassemble(); 1673 assert!(dis.contains("CreateClosure"), "got:\n{dis}"); 1674 let inner = &f.functions[0]; 1675 assert_eq!(inner.param_count, 2); 1676 } 1677 1678 #[test] 1679 fn test_compile_assignment() { 1680 let f = compile_src("var x = 1; x = x + 2;"); 1681 let dis = f.disassemble(); 1682 assert!(dis.contains("Add"), "got:\n{dis}"); 1683 assert!( 1684 dis.contains("Move"), 1685 "assignment should produce Move:\n{dis}" 1686 ); 1687 } 1688 1689 #[test] 1690 fn test_compile_compound_assignment() { 1691 let f = compile_src("var x = 10; x += 5;"); 1692 let dis = f.disassemble(); 1693 assert!(dis.contains("Add"), "got:\n{dis}"); 1694 } 1695 1696 #[test] 1697 fn test_compile_member_access() { 1698 let f = compile_src("var obj = {}; obj.x;"); 1699 let dis = f.disassemble(); 1700 assert!(dis.contains("CreateObject"), "got:\n{dis}"); 1701 assert!(dis.contains("GetPropertyByName"), "got:\n{dis}"); 1702 } 1703 1704 #[test] 1705 fn test_compile_computed_member() { 1706 let f = compile_src("var arr = []; arr[0];"); 1707 let dis = f.disassemble(); 1708 assert!(dis.contains("GetProperty"), "got:\n{dis}"); 1709 } 1710 1711 #[test] 1712 fn test_compile_object_literal() { 1713 let f = compile_src("var obj = { a: 1, b: 2 };"); 1714 let dis = f.disassemble(); 1715 assert!(dis.contains("CreateObject"), "got:\n{dis}"); 1716 assert!(dis.contains("SetPropertyByName"), "got:\n{dis}"); 1717 } 1718 1719 #[test] 1720 fn test_compile_array_literal() { 1721 let f = compile_src("[1, 2, 3];"); 1722 let dis = f.disassemble(); 1723 assert!(dis.contains("CreateArray"), "got:\n{dis}"); 1724 assert!(dis.contains("SetProperty"), "got:\n{dis}"); 1725 } 1726 1727 #[test] 1728 fn test_compile_conditional() { 1729 let f = compile_src("true ? 1 : 2;"); 1730 let dis = f.disassemble(); 1731 assert!(dis.contains("JumpIfFalse"), "got:\n{dis}"); 1732 } 1733 1734 #[test] 1735 fn test_compile_logical_and() { 1736 let f = compile_src("true && false;"); 1737 let dis = f.disassemble(); 1738 assert!(dis.contains("JumpIfFalse"), "short-circuit:\n{dis}"); 1739 } 1740 1741 #[test] 1742 fn test_compile_logical_or() { 1743 let f = compile_src("false || true;"); 1744 let dis = f.disassemble(); 1745 assert!(dis.contains("JumpIfTrue"), "short-circuit:\n{dis}"); 1746 } 1747 1748 #[test] 1749 fn test_compile_typeof() { 1750 let f = compile_src("typeof 42;"); 1751 let dis = f.disassemble(); 1752 assert!(dis.contains("TypeOf"), "got:\n{dis}"); 1753 } 1754 1755 #[test] 1756 fn test_compile_unary_minus() { 1757 let f = compile_src("-42;"); 1758 let dis = f.disassemble(); 1759 assert!(dis.contains("Neg"), "got:\n{dis}"); 1760 } 1761 1762 #[test] 1763 fn test_compile_not() { 1764 let f = compile_src("!true;"); 1765 let dis = f.disassemble(); 1766 assert!(dis.contains("LogicalNot"), "got:\n{dis}"); 1767 } 1768 1769 #[test] 1770 fn test_compile_return() { 1771 let f = compile_src("function f() { return 42; }"); 1772 let inner = &f.functions[0]; 1773 let dis = inner.disassemble(); 1774 assert!(dis.contains("Return"), "got:\n{dis}"); 1775 } 1776 1777 #[test] 1778 fn test_compile_empty_return() { 1779 let f = compile_src("function f() { return; }"); 1780 let inner = &f.functions[0]; 1781 let dis = inner.disassemble(); 1782 assert!(dis.contains("LoadUndefined"), "got:\n{dis}"); 1783 assert!(dis.contains("Return"), "got:\n{dis}"); 1784 } 1785 1786 #[test] 1787 fn test_compile_throw() { 1788 let f = compile_src("function f() { throw 42; }"); 1789 let inner = &f.functions[0]; 1790 let dis = inner.disassemble(); 1791 assert!(dis.contains("Throw"), "got:\n{dis}"); 1792 } 1793 1794 #[test] 1795 fn test_compile_this() { 1796 let f = compile_src("this;"); 1797 let dis = f.disassemble(); 1798 assert!(dis.contains("LoadGlobal"), "got:\n{dis}"); 1799 assert!(f.names.contains(&"this".to_string())); 1800 } 1801 1802 #[test] 1803 fn test_compile_global_var() { 1804 let f = compile_src("console;"); 1805 let dis = f.disassemble(); 1806 assert!(dis.contains("LoadGlobal"), "got:\n{dis}"); 1807 assert!(f.names.contains(&"console".to_string())); 1808 } 1809 1810 #[test] 1811 fn test_compile_template_literal() { 1812 let f = compile_src("`hello`;"); 1813 assert!( 1814 f.constants.contains(&Constant::String("hello".into())), 1815 "constants: {:?}", 1816 f.constants 1817 ); 1818 } 1819 1820 #[test] 1821 fn test_compile_switch() { 1822 let f = compile_src("switch (1) { case 1: 42; break; case 2: 99; break; }"); 1823 let dis = f.disassemble(); 1824 assert!(dis.contains("StrictEq"), "got:\n{dis}"); 1825 } 1826 1827 #[test] 1828 fn test_compile_class() { 1829 let f = compile_src("class Foo { constructor() {} greet() { return 1; } }"); 1830 let dis = f.disassemble(); 1831 assert!(dis.contains("CreateClosure"), "got:\n{dis}"); 1832 } 1833 1834 #[test] 1835 fn test_compile_update_prefix() { 1836 let f = compile_src("var x = 0; ++x;"); 1837 let dis = f.disassemble(); 1838 assert!(dis.contains("Add"), "got:\n{dis}"); 1839 } 1840 1841 #[test] 1842 fn test_compile_comparison() { 1843 let f = compile_src("1 === 2;"); 1844 let dis = f.disassemble(); 1845 assert!(dis.contains("StrictEq"), "got:\n{dis}"); 1846 } 1847 1848 #[test] 1849 fn test_compile_bitwise() { 1850 let f = compile_src("1 & 2;"); 1851 let dis = f.disassemble(); 1852 assert!(dis.contains("BitAnd"), "got:\n{dis}"); 1853 } 1854 1855 #[test] 1856 fn test_compile_void() { 1857 let f = compile_src("void 0;"); 1858 let dis = f.disassemble(); 1859 assert!(dis.contains("Void"), "got:\n{dis}"); 1860 } 1861 1862 #[test] 1863 fn test_disassembler_output_format() { 1864 let f = compile_src("var x = 42; x + 1;"); 1865 let dis = f.disassemble(); 1866 // Should contain function header. 1867 assert!(dis.contains("function <main>")); 1868 // Should contain code section. 1869 assert!(dis.contains("code:")); 1870 // Should have hex offsets. 1871 assert!(dis.contains("0000")); 1872 } 1873 1874 #[test] 1875 fn test_register_allocation_is_minimal() { 1876 // `var a = 1; var b = 2; a + b;` should use few registers. 1877 let f = compile_src("var a = 1; var b = 2; a + b;"); 1878 // r0 = result, r1 = a, r2 = b, r3/r4 = temps for addition 1879 assert!( 1880 f.register_count <= 6, 1881 "too many registers: {}", 1882 f.register_count 1883 ); 1884 } 1885 1886 #[test] 1887 fn test_nested_function_closure() { 1888 let f = compile_src("function outer() { function inner() { return 1; } return inner; }"); 1889 assert_eq!(f.functions.len(), 1); 1890 let outer = &f.functions[0]; 1891 assert_eq!(outer.name, "outer"); 1892 assert_eq!(outer.functions.len(), 1); 1893 let inner = &outer.functions[0]; 1894 assert_eq!(inner.name, "inner"); 1895 } 1896 1897 #[test] 1898 fn test_for_with_no_parts() { 1899 // `for (;;) { break; }` — infinite loop with immediate break. 1900 let f = compile_src("for (;;) { break; }"); 1901 let dis = f.disassemble(); 1902 assert!(dis.contains("Jump"), "got:\n{dis}"); 1903 } 1904 1905 #[test] 1906 fn test_for_continue_targets_update() { 1907 // `continue` in a for-loop must jump to the update expression, not back 1908 // to the condition check. Verify the continue jump goes to the Add (i + 1) 1909 // rather than to the LessThan condition. 1910 let f = compile_src("for (var i = 0; i < 10; i = i + 1) { continue; }"); 1911 let dis = f.disassemble(); 1912 // The for-loop should contain: LessThan (test), JumpIfFalse (exit), 1913 // Jump (continue), Add (update), Jump (back to test). 1914 assert!(dis.contains("LessThan"), "missing test: {dis}"); 1915 assert!(dis.contains("Add"), "missing update: {dis}"); 1916 // There should be at least 2 Jump instructions (continue + back-edge). 1917 let jump_count = dis.matches("Jump ").count(); 1918 assert!( 1919 jump_count >= 2, 1920 "expected >= 2 jumps for continue + back-edge, got {jump_count}: {dis}" 1921 ); 1922 } 1923 1924 #[test] 1925 fn test_do_while_continue_targets_condition() { 1926 // `continue` in do-while must jump to the condition, not the body start. 1927 let f = compile_src("var i = 0; do { i = i + 1; continue; } while (i < 5);"); 1928 let dis = f.disassemble(); 1929 assert!(dis.contains("LessThan"), "missing condition: {dis}"); 1930 assert!(dis.contains("JumpIfTrue"), "missing back-edge: {dis}"); 1931 } 1932 1933 #[test] 1934 fn test_switch_default_case() { 1935 // Default case must not corrupt bytecode. 1936 let f = compile_src("switch (1) { case 1: 10; break; default: 20; break; }"); 1937 let dis = f.disassemble(); 1938 assert!(dis.contains("StrictEq"), "missing case test: {dis}"); 1939 // The first instruction should NOT be corrupted. 1940 assert!( 1941 dis.contains("LoadUndefined r0"), 1942 "first instruction corrupted: {dis}" 1943 ); 1944 } 1945 1946 #[test] 1947 fn test_switch_only_default() { 1948 // Switch with only a default case. 1949 let f = compile_src("switch (42) { default: 99; }"); 1950 let dis = f.disassemble(); 1951 // Should compile without panicking and contain the default body. 1952 assert!(dis.contains("LoadInt8"), "got:\n{dis}"); 1953 } 1954 1955 #[test] 1956 fn test_class_empty_constructor_has_return() { 1957 // A class without an explicit constructor should produce a function with Return. 1958 let f = compile_src("class Foo {}"); 1959 assert!(!f.functions.is_empty(), "should have constructor function"); 1960 let ctor = &f.functions[0]; 1961 let dis = ctor.disassemble(); 1962 assert!( 1963 dis.contains("Return"), 1964 "empty constructor must have Return: {dis}" 1965 ); 1966 } 1967 1968 #[test] 1969 fn test_class_expression_compiles_methods() { 1970 // Class expression should compile methods, not just the constructor. 1971 let f = compile_src("var C = class { greet() { return 1; } };"); 1972 let dis = f.disassemble(); 1973 assert!( 1974 dis.contains("SetPropertyByName"), 1975 "method should be set as property: {dis}" 1976 ); 1977 } 1978}