we (web engine): Experimental web browser project to understand the limits of Claude
2
fork

Configure Feed

Select the types of activity you want to include in your feed.

Implement JS closures, const immutability, and method-call this binding

Add upvalue-based closure system using GC-allocated cells:
- New bytecode ops: NewCell, CellLoad, CellStore, LoadUpvalue, StoreUpvalue
- UpvalueDef metadata on Function for closure capture resolution
- HeapObject::Cell variant for mutable captured variable storage
- Free variable analysis with transitive capture support
- Captured parameters are boxed into cells at function entry
- const declarations enforce immutability at compile time
- Method calls (obj.method()) set this before invocation

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

+1340 -68
+77
crates/js/src/bytecode.rs
··· 152 152 PushExceptionHandler = 0x76, 153 153 /// PopExceptionHandler — remove the current exception handler 154 154 PopExceptionHandler = 0x77, 155 + /// NewCell dst — allocate a new GC cell initialized to undefined 156 + NewCell = 0x78, 157 + /// CellLoad dst, cell_reg — read the value stored in the cell 158 + CellLoad = 0x79, 159 + /// CellStore cell_reg, src — write a value into the cell 160 + CellStore = 0x7A, 161 + /// LoadUpvalue dst, idx(u8) — load from the closure's captured upvalue cell 162 + LoadUpvalue = 0x7B, 163 + /// StoreUpvalue idx(u8), src — store into the closure's captured upvalue cell 164 + StoreUpvalue = 0x7C, 155 165 } 156 166 157 167 impl Op { ··· 215 225 0x75 => Some(Op::GetPrototype), 216 226 0x76 => Some(Op::PushExceptionHandler), 217 227 0x77 => Some(Op::PopExceptionHandler), 228 + 0x78 => Some(Op::NewCell), 229 + 0x79 => Some(Op::CellLoad), 230 + 0x7A => Some(Op::CellStore), 231 + 0x7B => Some(Op::LoadUpvalue), 232 + 0x7C => Some(Op::StoreUpvalue), 218 233 _ => None, 219 234 } 220 235 } ··· 227 242 String(String), 228 243 } 229 244 245 + /// Describes how a closure captures a single variable from its enclosing scope. 246 + #[derive(Debug, Clone)] 247 + pub struct UpvalueDef { 248 + /// If true, `index` refers to a register in the immediately enclosing function 249 + /// (which must hold a cell GcRef). If false, `index` refers to an upvalue slot 250 + /// of the enclosing function (transitive capture). 251 + pub is_local: bool, 252 + /// Register index (if `is_local`) or upvalue index (if not) in the parent. 253 + pub index: u8, 254 + } 255 + 230 256 /// A compiled bytecode function. 231 257 #[derive(Debug, Clone)] 232 258 pub struct Function { ··· 246 272 pub functions: Vec<Function>, 247 273 /// Source map: bytecode offset → source line (sorted by offset). 248 274 pub source_map: Vec<(u32, u32)>, 275 + /// Upvalue definitions: how this function captures variables from its parent. 276 + pub upvalue_defs: Vec<UpvalueDef>, 249 277 } 250 278 251 279 impl Function { ··· 259 287 names: Vec::new(), 260 288 functions: Vec::new(), 261 289 source_map: Vec::new(), 290 + upvalue_defs: Vec::new(), 262 291 } 263 292 } 264 293 } ··· 484 513 self.emit_u8(value as u8); 485 514 } 486 515 516 + /// Emit: LoadUpvalue dst, idx 517 + pub fn emit_load_upvalue(&mut self, dst: Reg, idx: u8) { 518 + self.emit_u8(Op::LoadUpvalue as u8); 519 + self.emit_u8(dst); 520 + self.emit_u8(idx); 521 + } 522 + 523 + /// Emit: StoreUpvalue idx, src 524 + pub fn emit_store_upvalue(&mut self, idx: u8, src: Reg) { 525 + self.emit_u8(Op::StoreUpvalue as u8); 526 + self.emit_u8(idx); 527 + self.emit_u8(src); 528 + } 529 + 487 530 /// Add a source map entry: current bytecode offset → source line. 488 531 pub fn add_source_map(&mut self, line: u32) { 489 532 let offset = self.offset() as u32; ··· 807 850 format!("PushExceptionHandler r{catch_reg}, @{target:04X}") 808 851 } 809 852 Op::PopExceptionHandler => "PopExceptionHandler".to_string(), 853 + Op::NewCell => { 854 + let dst = code[pc]; 855 + pc += 1; 856 + format!("NewCell r{dst}") 857 + } 858 + Op::CellLoad => { 859 + let dst = code[pc]; 860 + let cell = code[pc + 1]; 861 + pc += 2; 862 + format!("CellLoad r{dst}, r{cell}") 863 + } 864 + Op::CellStore => { 865 + let cell = code[pc]; 866 + let src = code[pc + 1]; 867 + pc += 2; 868 + format!("CellStore r{cell}, r{src}") 869 + } 870 + Op::LoadUpvalue => { 871 + let dst = code[pc]; 872 + let idx = code[pc + 1]; 873 + pc += 2; 874 + format!("LoadUpvalue r{dst}, uv{idx}") 875 + } 876 + Op::StoreUpvalue => { 877 + let idx = code[pc]; 878 + let src = code[pc + 1]; 879 + pc += 2; 880 + format!("StoreUpvalue uv{idx}, r{src}") 881 + } 810 882 }; 811 883 out.push_str(&format!(" {offset:04X} {line}\n")); 812 884 } ··· 895 967 Op::GetPrototype, 896 968 Op::PushExceptionHandler, 897 969 Op::PopExceptionHandler, 970 + Op::NewCell, 971 + Op::CellLoad, 972 + Op::CellStore, 973 + Op::LoadUpvalue, 974 + Op::StoreUpvalue, 898 975 ]; 899 976 for op in ops { 900 977 assert_eq!(
+951 -57
crates/js/src/compiler.rs
··· 7 7 use crate::ast::*; 8 8 use crate::bytecode::*; 9 9 use crate::JsError; 10 + use std::collections::HashSet; 10 11 11 12 /// Compiler state for a single function scope. 12 13 struct FunctionCompiler { ··· 17 18 next_reg: u8, 18 19 /// Stack of loop contexts for break/continue. 19 20 loop_stack: Vec<LoopCtx>, 21 + /// Upvalues captured from the parent scope (used in inner functions). 22 + upvalues: Vec<UpvalueEntry>, 23 + /// Set of local variable names that are captured by inner functions. 24 + /// Pre-populated before compilation by scanning inner function bodies. 25 + captured_names: HashSet<String>, 20 26 } 21 27 22 28 #[derive(Debug, Clone)] 23 29 struct Local { 24 30 name: String, 25 31 reg: Reg, 32 + /// Whether this variable is captured by an inner function (stored in a cell). 33 + is_captured: bool, 34 + /// Whether this variable was declared with `const`. 35 + is_const: bool, 36 + } 37 + 38 + /// An upvalue entry tracking how this function captures an outer variable. 39 + struct UpvalueEntry { 40 + /// Name of the captured variable (for dedup during resolution). 41 + name: String, 42 + /// The resolved upvalue definition. 43 + def: UpvalueDef, 44 + /// Whether the original declaration was `const`. 45 + is_const: bool, 26 46 } 27 47 28 48 struct LoopCtx { ··· 41 61 locals: Vec::new(), 42 62 next_reg: 0, 43 63 loop_stack: Vec::new(), 64 + upvalues: Vec::new(), 65 + captured_names: HashSet::new(), 44 66 } 45 67 } 46 68 ··· 64 86 self.next_reg -= 1; 65 87 } 66 88 67 - /// Look up a local variable by name. 89 + /// Look up a local variable by name, returning full info. 90 + fn find_local_info(&self, name: &str) -> Option<&Local> { 91 + self.locals.iter().rev().find(|l| l.name == name) 92 + } 93 + 94 + /// Look up a local variable by name (register only). 68 95 fn find_local(&self, name: &str) -> Option<Reg> { 69 - self.locals 96 + self.find_local_info(name).map(|l| l.reg) 97 + } 98 + 99 + /// Look up an upvalue by name, returning its index. 100 + fn find_upvalue(&self, name: &str) -> Option<u8> { 101 + self.upvalues 70 102 .iter() 71 - .rev() 72 - .find(|l| l.name == name) 73 - .map(|l| l.reg) 103 + .position(|u| u.name == name) 104 + .map(|i| i as u8) 105 + } 106 + 107 + /// Check if an upvalue is const. 108 + fn is_upvalue_const(&self, idx: u8) -> bool { 109 + self.upvalues 110 + .get(idx as usize) 111 + .map(|u| u.is_const) 112 + .unwrap_or(false) 74 113 } 75 114 76 115 /// Define a local variable. 77 116 fn define_local(&mut self, name: &str) -> Reg { 117 + self.define_local_ext(name, false, false) 118 + } 119 + 120 + /// Define a local variable with capture and const flags. 121 + fn define_local_ext(&mut self, name: &str, is_captured: bool, is_const: bool) -> Reg { 78 122 let reg = self.alloc_reg(); 79 123 self.locals.push(Local { 80 124 name: name.to_string(), 81 125 reg, 126 + is_captured, 127 + is_const, 82 128 }); 83 129 reg 84 130 } 85 131 } 86 132 133 + // ── Free variable analysis ────────────────────────────────── 134 + 135 + /// Collect identifiers referenced inside `body` that are not declared locally 136 + /// (params or variable declarations within the body). This set represents the 137 + /// "free variables" of a function body — variables that must be captured. 138 + /// This includes transitive free variables from nested functions. 139 + fn collect_free_vars(params: &[Pattern], body: &[Stmt]) -> HashSet<String> { 140 + let mut declared = HashSet::new(); 141 + let mut referenced = HashSet::new(); 142 + 143 + // Params are local declarations. 144 + for p in params { 145 + collect_pattern_names(p, &mut declared); 146 + } 147 + 148 + // Collect declarations and references from the body. 149 + for stmt in body { 150 + collect_stmt_decls(stmt, &mut declared); 151 + } 152 + for stmt in body { 153 + collect_stmt_refs(stmt, &declared, &mut referenced); 154 + } 155 + 156 + // Also include transitive free variables from nested inner functions. 157 + // If an inner function references `x` and `x` is not declared in THIS scope, 158 + // then `x` is also a free variable of THIS function. 159 + let inner_caps = collect_inner_captures(body); 160 + for name in inner_caps { 161 + if !declared.contains(&name) { 162 + referenced.insert(name); 163 + } 164 + } 165 + 166 + referenced 167 + } 168 + 169 + /// Collect free variables from an arrow function body. 170 + fn collect_free_vars_arrow(params: &[Pattern], body: &ArrowBody) -> HashSet<String> { 171 + let mut declared = HashSet::new(); 172 + let mut referenced = HashSet::new(); 173 + 174 + for p in params { 175 + collect_pattern_names(p, &mut declared); 176 + } 177 + 178 + match body { 179 + ArrowBody::Expr(expr) => { 180 + collect_expr_refs(expr, &declared, &mut referenced); 181 + } 182 + ArrowBody::Block(stmts) => { 183 + for stmt in stmts { 184 + collect_stmt_decls(stmt, &mut declared); 185 + } 186 + for stmt in stmts { 187 + collect_stmt_refs(stmt, &declared, &mut referenced); 188 + } 189 + // Transitive free variables from nested functions. 190 + let inner_caps = collect_inner_captures(stmts); 191 + for name in inner_caps { 192 + if !declared.contains(&name) { 193 + referenced.insert(name); 194 + } 195 + } 196 + } 197 + } 198 + 199 + referenced 200 + } 201 + 202 + fn collect_pattern_names(pat: &Pattern, names: &mut HashSet<String>) { 203 + match &pat.kind { 204 + PatternKind::Identifier(name) => { 205 + names.insert(name.clone()); 206 + } 207 + PatternKind::Array { elements, rest } => { 208 + for elem in elements.iter().flatten() { 209 + collect_pattern_names(elem, names); 210 + } 211 + if let Some(rest) = rest { 212 + collect_pattern_names(rest, names); 213 + } 214 + } 215 + PatternKind::Object { properties, rest } => { 216 + for prop in properties { 217 + collect_pattern_names(&prop.value, names); 218 + } 219 + if let Some(rest) = rest { 220 + collect_pattern_names(rest, names); 221 + } 222 + } 223 + PatternKind::Assign { left, .. } => { 224 + collect_pattern_names(left, names); 225 + } 226 + } 227 + } 228 + 229 + /// Collect all variable/function declarations in a statement (not recursing into 230 + /// inner functions — those form their own scope). 231 + fn collect_stmt_decls(stmt: &Stmt, declared: &mut HashSet<String>) { 232 + match &stmt.kind { 233 + StmtKind::VarDecl { declarators, .. } => { 234 + for d in declarators { 235 + collect_pattern_names(&d.pattern, declared); 236 + } 237 + } 238 + StmtKind::FunctionDecl(f) => { 239 + if let Some(name) = &f.id { 240 + declared.insert(name.clone()); 241 + } 242 + } 243 + StmtKind::ClassDecl(c) => { 244 + if let Some(name) = &c.id { 245 + declared.insert(name.clone()); 246 + } 247 + } 248 + StmtKind::Block(stmts) => { 249 + for s in stmts { 250 + collect_stmt_decls(s, declared); 251 + } 252 + } 253 + StmtKind::If { 254 + consequent, 255 + alternate, 256 + .. 257 + } => { 258 + collect_stmt_decls(consequent, declared); 259 + if let Some(alt) = alternate { 260 + collect_stmt_decls(alt, declared); 261 + } 262 + } 263 + StmtKind::While { body, .. } 264 + | StmtKind::DoWhile { body, .. } 265 + | StmtKind::Labeled { body, .. } => { 266 + collect_stmt_decls(body, declared); 267 + } 268 + StmtKind::For { init, body, .. } => { 269 + if let Some(ForInit::VarDecl { declarators, .. }) = init { 270 + for d in declarators { 271 + collect_pattern_names(&d.pattern, declared); 272 + } 273 + } 274 + collect_stmt_decls(body, declared); 275 + } 276 + StmtKind::ForIn { left, body, .. } | StmtKind::ForOf { left, body, .. } => { 277 + if let ForInOfLeft::VarDecl { pattern, .. } = left { 278 + collect_pattern_names(pattern, declared); 279 + } 280 + collect_stmt_decls(body, declared); 281 + } 282 + StmtKind::Try { 283 + block, 284 + handler, 285 + finalizer, 286 + } => { 287 + for s in block { 288 + collect_stmt_decls(s, declared); 289 + } 290 + if let Some(h) = handler { 291 + if let Some(param) = &h.param { 292 + collect_pattern_names(param, declared); 293 + } 294 + for s in &h.body { 295 + collect_stmt_decls(s, declared); 296 + } 297 + } 298 + if let Some(fin) = finalizer { 299 + for s in fin { 300 + collect_stmt_decls(s, declared); 301 + } 302 + } 303 + } 304 + StmtKind::Switch { cases, .. } => { 305 + for case in cases { 306 + for s in &case.consequent { 307 + collect_stmt_decls(s, declared); 308 + } 309 + } 310 + } 311 + _ => {} 312 + } 313 + } 314 + 315 + /// Collect all identifier references in a statement, excluding inner function scopes. 316 + /// Identifiers that are in `declared` are local and skipped. 317 + fn collect_stmt_refs(stmt: &Stmt, declared: &HashSet<String>, refs: &mut HashSet<String>) { 318 + match &stmt.kind { 319 + StmtKind::Expr(expr) => collect_expr_refs(expr, declared, refs), 320 + StmtKind::Block(stmts) => { 321 + for s in stmts { 322 + collect_stmt_refs(s, declared, refs); 323 + } 324 + } 325 + StmtKind::VarDecl { declarators, .. } => { 326 + for d in declarators { 327 + if let Some(init) = &d.init { 328 + collect_expr_refs(init, declared, refs); 329 + } 330 + } 331 + } 332 + StmtKind::FunctionDecl(_) => { 333 + // Don't recurse into inner functions — they have their own scope. 334 + } 335 + StmtKind::If { 336 + test, 337 + consequent, 338 + alternate, 339 + } => { 340 + collect_expr_refs(test, declared, refs); 341 + collect_stmt_refs(consequent, declared, refs); 342 + if let Some(alt) = alternate { 343 + collect_stmt_refs(alt, declared, refs); 344 + } 345 + } 346 + StmtKind::While { test, body } => { 347 + collect_expr_refs(test, declared, refs); 348 + collect_stmt_refs(body, declared, refs); 349 + } 350 + StmtKind::DoWhile { body, test } => { 351 + collect_stmt_refs(body, declared, refs); 352 + collect_expr_refs(test, declared, refs); 353 + } 354 + StmtKind::For { 355 + init, 356 + test, 357 + update, 358 + body, 359 + } => { 360 + if let Some(init) = init { 361 + match init { 362 + ForInit::VarDecl { declarators, .. } => { 363 + for d in declarators { 364 + if let Some(init) = &d.init { 365 + collect_expr_refs(init, declared, refs); 366 + } 367 + } 368 + } 369 + ForInit::Expr(e) => collect_expr_refs(e, declared, refs), 370 + } 371 + } 372 + if let Some(t) = test { 373 + collect_expr_refs(t, declared, refs); 374 + } 375 + if let Some(u) = update { 376 + collect_expr_refs(u, declared, refs); 377 + } 378 + collect_stmt_refs(body, declared, refs); 379 + } 380 + StmtKind::ForIn { right, body, .. } | StmtKind::ForOf { right, body, .. } => { 381 + collect_expr_refs(right, declared, refs); 382 + collect_stmt_refs(body, declared, refs); 383 + } 384 + StmtKind::Return(Some(expr)) | StmtKind::Throw(expr) => { 385 + collect_expr_refs(expr, declared, refs); 386 + } 387 + StmtKind::Try { 388 + block, 389 + handler, 390 + finalizer, 391 + } => { 392 + for s in block { 393 + collect_stmt_refs(s, declared, refs); 394 + } 395 + if let Some(h) = handler { 396 + for s in &h.body { 397 + collect_stmt_refs(s, declared, refs); 398 + } 399 + } 400 + if let Some(fin) = finalizer { 401 + for s in fin { 402 + collect_stmt_refs(s, declared, refs); 403 + } 404 + } 405 + } 406 + StmtKind::Switch { 407 + discriminant, 408 + cases, 409 + } => { 410 + collect_expr_refs(discriminant, declared, refs); 411 + for case in cases { 412 + if let Some(test) = &case.test { 413 + collect_expr_refs(test, declared, refs); 414 + } 415 + for s in &case.consequent { 416 + collect_stmt_refs(s, declared, refs); 417 + } 418 + } 419 + } 420 + StmtKind::Labeled { body, .. } => { 421 + collect_stmt_refs(body, declared, refs); 422 + } 423 + _ => {} 424 + } 425 + } 426 + 427 + /// Collect identifier references in an expression. Does NOT recurse into 428 + /// inner function/arrow bodies (those form their own scope). 429 + fn collect_expr_refs(expr: &Expr, declared: &HashSet<String>, refs: &mut HashSet<String>) { 430 + match &expr.kind { 431 + ExprKind::Identifier(name) => { 432 + if !declared.contains(name) { 433 + refs.insert(name.clone()); 434 + } 435 + } 436 + ExprKind::Binary { left, right, .. } 437 + | ExprKind::Logical { left, right, .. } 438 + | ExprKind::Assignment { left, right, .. } => { 439 + collect_expr_refs(left, declared, refs); 440 + collect_expr_refs(right, declared, refs); 441 + } 442 + ExprKind::Unary { argument, .. } | ExprKind::Update { argument, .. } => { 443 + collect_expr_refs(argument, declared, refs); 444 + } 445 + ExprKind::Conditional { 446 + test, 447 + consequent, 448 + alternate, 449 + } => { 450 + collect_expr_refs(test, declared, refs); 451 + collect_expr_refs(consequent, declared, refs); 452 + collect_expr_refs(alternate, declared, refs); 453 + } 454 + ExprKind::Call { callee, arguments } | ExprKind::New { callee, arguments } => { 455 + collect_expr_refs(callee, declared, refs); 456 + for arg in arguments { 457 + collect_expr_refs(arg, declared, refs); 458 + } 459 + } 460 + ExprKind::Member { 461 + object, 462 + property, 463 + computed, 464 + .. 465 + } => { 466 + collect_expr_refs(object, declared, refs); 467 + if *computed { 468 + collect_expr_refs(property, declared, refs); 469 + } 470 + } 471 + ExprKind::Array(elements) => { 472 + for elem in elements.iter().flatten() { 473 + match elem { 474 + ArrayElement::Expr(e) | ArrayElement::Spread(e) => { 475 + collect_expr_refs(e, declared, refs); 476 + } 477 + } 478 + } 479 + } 480 + ExprKind::Object(props) => { 481 + for prop in props { 482 + if let PropertyKey::Computed(e) = &prop.key { 483 + collect_expr_refs(e, declared, refs); 484 + } 485 + if let Some(val) = &prop.value { 486 + collect_expr_refs(val, declared, refs); 487 + } 488 + } 489 + } 490 + ExprKind::Sequence(exprs) => { 491 + for e in exprs { 492 + collect_expr_refs(e, declared, refs); 493 + } 494 + } 495 + ExprKind::Spread(inner) => { 496 + collect_expr_refs(inner, declared, refs); 497 + } 498 + ExprKind::TemplateLiteral { expressions, .. } => { 499 + for e in expressions { 500 + collect_expr_refs(e, declared, refs); 501 + } 502 + } 503 + // Function/Arrow/Class bodies are new scopes — don't recurse. 504 + ExprKind::Function(_) | ExprKind::Arrow { .. } | ExprKind::Class(_) => {} 505 + _ => {} 506 + } 507 + } 508 + 509 + /// Collect the free variables of ALL inner functions/arrows within a list of 510 + /// statements. Returns the set of outer-scope names they reference. 511 + fn collect_inner_captures(stmts: &[Stmt]) -> HashSet<String> { 512 + let mut captures = HashSet::new(); 513 + for stmt in stmts { 514 + collect_inner_captures_stmt(stmt, &mut captures); 515 + } 516 + captures 517 + } 518 + 519 + fn collect_inner_captures_stmt(stmt: &Stmt, caps: &mut HashSet<String>) { 520 + match &stmt.kind { 521 + StmtKind::FunctionDecl(f) => { 522 + let fv = collect_free_vars(&f.params, &f.body); 523 + caps.extend(fv); 524 + } 525 + StmtKind::Expr(expr) => collect_inner_captures_expr(expr, caps), 526 + StmtKind::VarDecl { declarators, .. } => { 527 + for d in declarators { 528 + if let Some(init) = &d.init { 529 + collect_inner_captures_expr(init, caps); 530 + } 531 + } 532 + } 533 + StmtKind::Block(stmts) => { 534 + for s in stmts { 535 + collect_inner_captures_stmt(s, caps); 536 + } 537 + } 538 + StmtKind::If { 539 + test, 540 + consequent, 541 + alternate, 542 + } => { 543 + collect_inner_captures_expr(test, caps); 544 + collect_inner_captures_stmt(consequent, caps); 545 + if let Some(alt) = alternate { 546 + collect_inner_captures_stmt(alt, caps); 547 + } 548 + } 549 + StmtKind::While { test, body } => { 550 + collect_inner_captures_expr(test, caps); 551 + collect_inner_captures_stmt(body, caps); 552 + } 553 + StmtKind::DoWhile { body, test } => { 554 + collect_inner_captures_stmt(body, caps); 555 + collect_inner_captures_expr(test, caps); 556 + } 557 + StmtKind::For { 558 + init, 559 + test, 560 + update, 561 + body, 562 + } => { 563 + if let Some(ForInit::Expr(e)) = init { 564 + collect_inner_captures_expr(e, caps); 565 + } 566 + if let Some(ForInit::VarDecl { declarators, .. }) = init { 567 + for d in declarators { 568 + if let Some(init) = &d.init { 569 + collect_inner_captures_expr(init, caps); 570 + } 571 + } 572 + } 573 + if let Some(t) = test { 574 + collect_inner_captures_expr(t, caps); 575 + } 576 + if let Some(u) = update { 577 + collect_inner_captures_expr(u, caps); 578 + } 579 + collect_inner_captures_stmt(body, caps); 580 + } 581 + StmtKind::ForIn { right, body, .. } | StmtKind::ForOf { right, body, .. } => { 582 + collect_inner_captures_expr(right, caps); 583 + collect_inner_captures_stmt(body, caps); 584 + } 585 + StmtKind::Return(Some(expr)) | StmtKind::Throw(expr) => { 586 + collect_inner_captures_expr(expr, caps); 587 + } 588 + StmtKind::Try { 589 + block, 590 + handler, 591 + finalizer, 592 + } => { 593 + for s in block { 594 + collect_inner_captures_stmt(s, caps); 595 + } 596 + if let Some(h) = handler { 597 + for s in &h.body { 598 + collect_inner_captures_stmt(s, caps); 599 + } 600 + } 601 + if let Some(fin) = finalizer { 602 + for s in fin { 603 + collect_inner_captures_stmt(s, caps); 604 + } 605 + } 606 + } 607 + StmtKind::Switch { 608 + discriminant, 609 + cases, 610 + } => { 611 + collect_inner_captures_expr(discriminant, caps); 612 + for case in cases { 613 + if let Some(test) = &case.test { 614 + collect_inner_captures_expr(test, caps); 615 + } 616 + for s in &case.consequent { 617 + collect_inner_captures_stmt(s, caps); 618 + } 619 + } 620 + } 621 + StmtKind::Labeled { body, .. } => { 622 + collect_inner_captures_stmt(body, caps); 623 + } 624 + _ => {} 625 + } 626 + } 627 + 628 + fn collect_inner_captures_expr(expr: &Expr, caps: &mut HashSet<String>) { 629 + match &expr.kind { 630 + ExprKind::Function(f) => { 631 + let fv = collect_free_vars(&f.params, &f.body); 632 + caps.extend(fv); 633 + } 634 + ExprKind::Arrow { params, body, .. } => { 635 + let fv = collect_free_vars_arrow(params, body); 636 + caps.extend(fv); 637 + } 638 + ExprKind::Binary { left, right, .. } 639 + | ExprKind::Logical { left, right, .. } 640 + | ExprKind::Assignment { left, right, .. } => { 641 + collect_inner_captures_expr(left, caps); 642 + collect_inner_captures_expr(right, caps); 643 + } 644 + ExprKind::Unary { argument, .. } | ExprKind::Update { argument, .. } => { 645 + collect_inner_captures_expr(argument, caps); 646 + } 647 + ExprKind::Conditional { 648 + test, 649 + consequent, 650 + alternate, 651 + } => { 652 + collect_inner_captures_expr(test, caps); 653 + collect_inner_captures_expr(consequent, caps); 654 + collect_inner_captures_expr(alternate, caps); 655 + } 656 + ExprKind::Call { callee, arguments } | ExprKind::New { callee, arguments } => { 657 + collect_inner_captures_expr(callee, caps); 658 + for arg in arguments { 659 + collect_inner_captures_expr(arg, caps); 660 + } 661 + } 662 + ExprKind::Member { 663 + object, 664 + property, 665 + computed, 666 + .. 667 + } => { 668 + collect_inner_captures_expr(object, caps); 669 + if *computed { 670 + collect_inner_captures_expr(property, caps); 671 + } 672 + } 673 + ExprKind::Array(elements) => { 674 + for elem in elements.iter().flatten() { 675 + match elem { 676 + ArrayElement::Expr(e) | ArrayElement::Spread(e) => { 677 + collect_inner_captures_expr(e, caps); 678 + } 679 + } 680 + } 681 + } 682 + ExprKind::Object(props) => { 683 + for prop in props { 684 + if let PropertyKey::Computed(e) = &prop.key { 685 + collect_inner_captures_expr(e, caps); 686 + } 687 + if let Some(val) = &prop.value { 688 + collect_inner_captures_expr(val, caps); 689 + } 690 + } 691 + } 692 + ExprKind::Sequence(exprs) => { 693 + for e in exprs { 694 + collect_inner_captures_expr(e, caps); 695 + } 696 + } 697 + ExprKind::Spread(inner) => { 698 + collect_inner_captures_expr(inner, caps); 699 + } 700 + ExprKind::TemplateLiteral { expressions, .. } => { 701 + for e in expressions { 702 + collect_inner_captures_expr(e, caps); 703 + } 704 + } 705 + ExprKind::Class(c) => { 706 + for member in &c.body { 707 + if let ClassMemberKind::Method { value, .. } = &member.kind { 708 + let fv = collect_free_vars(&value.params, &value.body); 709 + caps.extend(fv); 710 + } 711 + } 712 + } 713 + _ => {} 714 + } 715 + } 716 + 87 717 /// Compile a parsed program into a top-level bytecode function. 88 718 pub fn compile(program: &Program) -> Result<Function, JsError> { 89 719 let mut fc = FunctionCompiler::new("<main>".into(), 0); 720 + 721 + // Pre-scan to find which top-level locals are captured by inner functions. 722 + fc.captured_names = collect_inner_captures(&program.body); 90 723 91 724 // Reserve r0 for the implicit return value. 92 725 let result_reg = fc.alloc_reg(); ··· 125 758 fc.next_reg = saved_next; 126 759 } 127 760 128 - StmtKind::VarDecl { 129 - kind: _, 130 - declarators, 131 - } => { 761 + StmtKind::VarDecl { kind, declarators } => { 132 762 for decl in declarators { 133 - compile_var_declarator(fc, decl)?; 763 + compile_var_declarator(fc, decl, *kind)?; 134 764 } 135 765 } 136 766 ··· 433 1063 434 1064 // ── Variable declarations ─────────────────────────────────── 435 1065 436 - fn compile_var_declarator(fc: &mut FunctionCompiler, decl: &VarDeclarator) -> Result<(), JsError> { 1066 + fn compile_var_declarator( 1067 + fc: &mut FunctionCompiler, 1068 + decl: &VarDeclarator, 1069 + kind: VarKind, 1070 + ) -> Result<(), JsError> { 437 1071 match &decl.pattern.kind { 438 1072 PatternKind::Identifier(name) => { 439 - let reg = fc.define_local(name); 440 - if let Some(init) = &decl.init { 1073 + let is_const = kind == VarKind::Const; 1074 + let is_captured = fc.captured_names.contains(name.as_str()); 1075 + 1076 + if is_const && decl.init.is_none() { 1077 + return Err(JsError::SyntaxError( 1078 + "Missing initializer in const declaration".into(), 1079 + )); 1080 + } 1081 + 1082 + let reg = fc.define_local_ext(name, is_captured, is_const); 1083 + 1084 + if is_captured { 1085 + // Allocate a cell for this variable. 1086 + fc.builder.emit_reg(Op::NewCell, reg); 1087 + if let Some(init) = &decl.init { 1088 + let tmp = fc.alloc_reg(); 1089 + compile_expr(fc, init, tmp)?; 1090 + fc.builder.emit_reg_reg(Op::CellStore, reg, tmp); 1091 + fc.free_reg(tmp); 1092 + } 1093 + // No init => cell stays undefined (already the default). 1094 + } else if let Some(init) = &decl.init { 441 1095 compile_expr(fc, init, reg)?; 442 1096 } else { 443 1097 fc.builder.emit_reg(Op::LoadUndefined, reg); ··· 533 1187 534 1188 fn compile_function_decl(fc: &mut FunctionCompiler, func_def: &FunctionDef) -> Result<(), JsError> { 535 1189 let name = func_def.id.clone().unwrap_or_default(); 536 - let inner = compile_function_body(func_def)?; 1190 + let inner = compile_function_body_with_captures(fc, func_def)?; 537 1191 let func_idx = fc.builder.add_function(inner); 538 1192 539 - let reg = fc.define_local(&name); 540 - fc.builder.emit_reg_u16(Op::CreateClosure, reg, func_idx); 1193 + let is_captured = fc.captured_names.contains(name.as_str()); 1194 + let reg = fc.define_local_ext(&name, is_captured, false); 541 1195 542 - // Also store as global so inner/recursive calls via LoadGlobal can find it. 543 - if !name.is_empty() { 544 - let name_idx = fc.builder.add_name(&name); 545 - fc.builder.emit_store_global(name_idx, reg); 1196 + if is_captured { 1197 + // Create a cell, then create the closure into a temp, then store into cell. 1198 + fc.builder.emit_reg(Op::NewCell, reg); 1199 + let tmp = fc.alloc_reg(); 1200 + fc.builder.emit_reg_u16(Op::CreateClosure, tmp, func_idx); 1201 + fc.builder.emit_reg_reg(Op::CellStore, reg, tmp); 1202 + // Also store as global. 1203 + if !name.is_empty() { 1204 + let name_idx = fc.builder.add_name(&name); 1205 + fc.builder.emit_store_global(name_idx, tmp); 1206 + } 1207 + fc.free_reg(tmp); 1208 + } else { 1209 + fc.builder.emit_reg_u16(Op::CreateClosure, reg, func_idx); 1210 + // Also store as global so inner/recursive calls via LoadGlobal can find it. 1211 + if !name.is_empty() { 1212 + let name_idx = fc.builder.add_name(&name); 1213 + fc.builder.emit_store_global(name_idx, reg); 1214 + } 546 1215 } 547 1216 Ok(()) 548 1217 } 549 1218 550 - fn compile_function_body(func_def: &FunctionDef) -> Result<Function, JsError> { 1219 + /// Compile a function body, resolving upvalue captures from the parent scope. 1220 + fn compile_function_body_with_captures( 1221 + parent: &mut FunctionCompiler, 1222 + func_def: &FunctionDef, 1223 + ) -> Result<Function, JsError> { 1224 + // 1. Collect free variables of this inner function. 1225 + let free_vars = collect_free_vars(&func_def.params, &func_def.body); 1226 + 1227 + // 2. Build upvalue list by resolving free vars against the parent scope. 1228 + let mut upvalue_entries = Vec::new(); 1229 + for name in &free_vars { 1230 + if let Some(local) = parent.find_local_info(name) { 1231 + let reg = local.reg; 1232 + let is_const = local.is_const; 1233 + // Mark the parent's local as captured (if not already). 1234 + // We need to update the parent's local, so find the index and mutate. 1235 + if let Some(l) = parent.locals.iter_mut().rev().find(|l| l.name == *name) { 1236 + l.is_captured = true; 1237 + } 1238 + upvalue_entries.push(UpvalueEntry { 1239 + name: name.clone(), 1240 + def: UpvalueDef { 1241 + is_local: true, 1242 + index: reg, 1243 + }, 1244 + is_const, 1245 + }); 1246 + } else if let Some(parent_uv_idx) = parent.find_upvalue(name) { 1247 + // Transitive capture: the parent captures it from its own parent. 1248 + let is_const = parent.is_upvalue_const(parent_uv_idx); 1249 + upvalue_entries.push(UpvalueEntry { 1250 + name: name.clone(), 1251 + def: UpvalueDef { 1252 + is_local: false, 1253 + index: parent_uv_idx, 1254 + }, 1255 + is_const, 1256 + }); 1257 + } 1258 + // If not found in parent or parent's upvalues, it must be a global — no upvalue needed. 1259 + } 1260 + 1261 + // 3. Compile the inner function with its own scope. 1262 + let mut inner = compile_function_body_inner(func_def, &upvalue_entries)?; 1263 + 1264 + // 4. Attach upvalue definitions to the compiled function. 1265 + inner.upvalue_defs = upvalue_entries.iter().map(|e| e.def.clone()).collect(); 1266 + 1267 + Ok(inner) 1268 + } 1269 + 1270 + /// Core function body compilation. The `upvalue_entries` tell this function which 1271 + /// outer variables it can access via LoadUpvalue/StoreUpvalue. 1272 + fn compile_function_body_inner( 1273 + func_def: &FunctionDef, 1274 + upvalue_entries: &[UpvalueEntry], 1275 + ) -> Result<Function, JsError> { 551 1276 let name = func_def.id.clone().unwrap_or_default(); 552 1277 let param_count = func_def.params.len().min(255) as u8; 553 1278 let mut inner = FunctionCompiler::new(name, param_count); 554 1279 1280 + // Copy upvalue entries into the inner compiler so it can resolve references. 1281 + for entry in upvalue_entries { 1282 + inner.upvalues.push(UpvalueEntry { 1283 + name: entry.name.clone(), 1284 + def: entry.def.clone(), 1285 + is_const: entry.is_const, 1286 + }); 1287 + } 1288 + 1289 + // Pre-scan to find which of this function's locals are captured by ITS inner functions. 1290 + let inner_caps = collect_inner_captures(&func_def.body); 1291 + inner.captured_names = inner_caps; 1292 + 555 1293 // Allocate registers for parameters. 556 1294 for p in &func_def.params { 557 - if let PatternKind::Identifier(name) = &p.kind { 558 - inner.define_local(name); 1295 + if let PatternKind::Identifier(pname) = &p.kind { 1296 + let is_captured = inner.captured_names.contains(pname.as_str()); 1297 + inner.define_local_ext(pname, is_captured, false); 559 1298 } else { 560 - // Destructuring param: allocate a register for the whole param, 561 - // then destructure from it. 562 1299 let _ = inner.alloc_reg(); 563 1300 } 564 1301 } 565 1302 1303 + // Box captured parameters into cells. 1304 + for p in &func_def.params { 1305 + if let PatternKind::Identifier(pname) = &p.kind { 1306 + if let Some(local) = inner.find_local_info(pname) { 1307 + if local.is_captured { 1308 + let reg = local.reg; 1309 + // Move param value to temp, allocate cell, store value into cell. 1310 + let tmp = inner.alloc_reg(); 1311 + inner.builder.emit_reg_reg(Op::Move, tmp, reg); 1312 + inner.builder.emit_reg(Op::NewCell, reg); 1313 + inner.builder.emit_reg_reg(Op::CellStore, reg, tmp); 1314 + inner.free_reg(tmp); 1315 + } 1316 + } 1317 + } 1318 + } 1319 + 566 1320 // Result register for the function body. 567 1321 let result_reg = inner.alloc_reg(); 568 1322 inner.builder.emit_reg(Op::LoadUndefined, result_reg); ··· 593 1347 594 1348 if let Some(member) = ctor { 595 1349 if let ClassMemberKind::Method { value, .. } = &member.kind { 596 - let inner = compile_function_body(value)?; 1350 + let inner = compile_function_body_with_captures(fc, value)?; 597 1351 let func_idx = fc.builder.add_function(inner); 598 1352 fc.builder.emit_reg_u16(Op::CreateClosure, reg, func_idx); 599 1353 } ··· 626 1380 PropertyKey::Identifier(s) | PropertyKey::String(s) => s.clone(), 627 1381 _ => continue, 628 1382 }; 629 - let inner = compile_function_body(value)?; 1383 + let inner = compile_function_body_with_captures(fc, value)?; 630 1384 let func_idx = fc.builder.add_function(inner); 631 1385 let method_reg = fc.alloc_reg(); 632 1386 fc.builder ··· 783 1537 // Init. 784 1538 if let Some(init) = init { 785 1539 match init { 786 - ForInit::VarDecl { 787 - kind: _, 788 - declarators, 789 - } => { 1540 + ForInit::VarDecl { kind, declarators } => { 790 1541 for decl in declarators { 791 - compile_var_declarator(fc, decl)?; 1542 + compile_var_declarator(fc, decl, *kind)?; 792 1543 } 793 1544 } 794 1545 ForInit::Expr(expr) => { ··· 977 1728 } 978 1729 979 1730 ExprKind::Identifier(name) => { 980 - if let Some(local_reg) = fc.find_local(name) { 981 - if local_reg != dst { 982 - fc.builder.emit_reg_reg(Op::Move, dst, local_reg); 1731 + if let Some(local) = fc.find_local_info(name) { 1732 + let reg = local.reg; 1733 + let captured = local.is_captured; 1734 + if captured { 1735 + fc.builder.emit_reg_reg(Op::CellLoad, dst, reg); 1736 + } else if reg != dst { 1737 + fc.builder.emit_reg_reg(Op::Move, dst, reg); 983 1738 } 1739 + } else if let Some(uv_idx) = fc.find_upvalue(name) { 1740 + fc.builder.emit_load_upvalue(dst, uv_idx); 984 1741 } else { 985 1742 // Global lookup. 986 1743 let ni = fc.builder.add_name(name); ··· 1169 1926 } 1170 1927 1171 1928 ExprKind::Call { callee, arguments } => { 1172 - let func_reg = fc.alloc_reg(); 1173 - compile_expr(fc, callee, func_reg)?; 1929 + // Detect method calls (obj.method()) to set `this`. 1930 + if let ExprKind::Member { 1931 + object, 1932 + property, 1933 + computed, 1934 + } = &callee.kind 1935 + { 1936 + // Layout: [obj_reg] [func_reg] [arg0] [arg1] ... 1937 + // We keep obj_reg alive so we can set `this` before the call. 1938 + let obj_reg = fc.alloc_reg(); 1939 + compile_expr(fc, object, obj_reg)?; 1940 + let func_reg = fc.alloc_reg(); 1941 + if !computed { 1942 + if let ExprKind::Identifier(name) = &property.kind { 1943 + let ni = fc.builder.add_name(name); 1944 + fc.builder.emit_get_prop_name(func_reg, obj_reg, ni); 1945 + } else { 1946 + let key_reg = fc.alloc_reg(); 1947 + compile_expr(fc, property, key_reg)?; 1948 + fc.builder 1949 + .emit_reg3(Op::GetProperty, func_reg, obj_reg, key_reg); 1950 + fc.free_reg(key_reg); 1951 + } 1952 + } else { 1953 + let key_reg = fc.alloc_reg(); 1954 + compile_expr(fc, property, key_reg)?; 1955 + fc.builder 1956 + .emit_reg3(Op::GetProperty, func_reg, obj_reg, key_reg); 1957 + fc.free_reg(key_reg); 1958 + } 1174 1959 1175 - let args_start = fc.next_reg; 1176 - let arg_count = arguments.len().min(255) as u8; 1177 - for arg in arguments { 1178 - let arg_reg = fc.alloc_reg(); 1179 - compile_expr(fc, arg, arg_reg)?; 1180 - } 1960 + // Set `this` to the receiver object before calling. 1961 + let this_ni = fc.builder.add_name("this"); 1962 + fc.builder.emit_store_global(this_ni, obj_reg); 1963 + 1964 + let args_start = fc.next_reg; 1965 + let arg_count = arguments.len().min(255) as u8; 1966 + for arg in arguments { 1967 + let arg_reg = fc.alloc_reg(); 1968 + compile_expr(fc, arg, arg_reg)?; 1969 + } 1181 1970 1182 - fc.builder.emit_call(dst, func_reg, args_start, arg_count); 1971 + fc.builder.emit_call(dst, func_reg, args_start, arg_count); 1183 1972 1184 - // Free argument registers (in reverse). 1185 - for _ in 0..arg_count { 1186 - fc.next_reg -= 1; 1973 + // Free in LIFO order: args, func_reg, obj_reg. 1974 + for _ in 0..arg_count { 1975 + fc.next_reg -= 1; 1976 + } 1977 + fc.free_reg(func_reg); 1978 + fc.free_reg(obj_reg); 1979 + } else { 1980 + let func_reg = fc.alloc_reg(); 1981 + compile_expr(fc, callee, func_reg)?; 1982 + 1983 + let args_start = fc.next_reg; 1984 + let arg_count = arguments.len().min(255) as u8; 1985 + for arg in arguments { 1986 + let arg_reg = fc.alloc_reg(); 1987 + compile_expr(fc, arg, arg_reg)?; 1988 + } 1989 + 1990 + fc.builder.emit_call(dst, func_reg, args_start, arg_count); 1991 + 1992 + for _ in 0..arg_count { 1993 + fc.next_reg -= 1; 1994 + } 1995 + fc.free_reg(func_reg); 1187 1996 } 1188 - fc.free_reg(func_reg); 1189 1997 } 1190 1998 1191 1999 ExprKind::New { callee, arguments } => { ··· 1308 2116 } 1309 2117 1310 2118 ExprKind::Function(func_def) => { 1311 - let inner = compile_function_body(func_def)?; 2119 + let inner = compile_function_body_with_captures(fc, func_def)?; 1312 2120 let func_idx = fc.builder.add_function(inner); 1313 2121 fc.builder.emit_reg_u16(Op::CreateClosure, dst, func_idx); 1314 2122 } ··· 1318 2126 body, 1319 2127 is_async: _, 1320 2128 } => { 2129 + // Collect free variables from the arrow body. 2130 + let free_vars = collect_free_vars_arrow(params, body); 2131 + 2132 + // Resolve upvalues against the parent scope. 2133 + let mut upvalue_entries = Vec::new(); 2134 + for name in &free_vars { 2135 + if let Some(local) = fc.find_local_info(name) { 2136 + let reg = local.reg; 2137 + let is_const = local.is_const; 2138 + if let Some(l) = fc.locals.iter_mut().rev().find(|l| l.name == *name) { 2139 + l.is_captured = true; 2140 + } 2141 + upvalue_entries.push(UpvalueEntry { 2142 + name: name.clone(), 2143 + def: UpvalueDef { 2144 + is_local: true, 2145 + index: reg, 2146 + }, 2147 + is_const, 2148 + }); 2149 + } else if let Some(parent_uv_idx) = fc.find_upvalue(name) { 2150 + let is_const = fc.is_upvalue_const(parent_uv_idx); 2151 + upvalue_entries.push(UpvalueEntry { 2152 + name: name.clone(), 2153 + def: UpvalueDef { 2154 + is_local: false, 2155 + index: parent_uv_idx, 2156 + }, 2157 + is_const, 2158 + }); 2159 + } 2160 + } 2161 + 1321 2162 let param_count = params.len().min(255) as u8; 1322 2163 let mut inner = FunctionCompiler::new("<arrow>".into(), param_count); 2164 + 2165 + // Copy upvalue entries. 2166 + for entry in &upvalue_entries { 2167 + inner.upvalues.push(UpvalueEntry { 2168 + name: entry.name.clone(), 2169 + def: entry.def.clone(), 2170 + is_const: entry.is_const, 2171 + }); 2172 + } 2173 + 2174 + // Pre-scan for inner captures within the arrow body. 2175 + match body { 2176 + ArrowBody::Expr(_) => {} 2177 + ArrowBody::Block(stmts) => { 2178 + inner.captured_names = collect_inner_captures(stmts); 2179 + } 2180 + } 2181 + 1323 2182 for p in params { 1324 - if let PatternKind::Identifier(name) = &p.kind { 1325 - inner.define_local(name); 2183 + if let PatternKind::Identifier(pname) = &p.kind { 2184 + let is_captured = inner.captured_names.contains(pname.as_str()); 2185 + inner.define_local_ext(pname, is_captured, false); 1326 2186 } else { 1327 2187 let _ = inner.alloc_reg(); 1328 2188 } 1329 2189 } 2190 + 2191 + // Box captured parameters. 2192 + for p in params { 2193 + if let PatternKind::Identifier(pname) = &p.kind { 2194 + if let Some(local) = inner.find_local_info(pname) { 2195 + if local.is_captured { 2196 + let reg = local.reg; 2197 + let tmp = inner.alloc_reg(); 2198 + inner.builder.emit_reg_reg(Op::Move, tmp, reg); 2199 + inner.builder.emit_reg(Op::NewCell, reg); 2200 + inner.builder.emit_reg_reg(Op::CellStore, reg, tmp); 2201 + inner.free_reg(tmp); 2202 + } 2203 + } 2204 + } 2205 + } 2206 + 1330 2207 let result = inner.alloc_reg(); 1331 2208 match body { 1332 2209 ArrowBody::Expr(e) => { ··· 1338 2215 } 1339 2216 } 1340 2217 inner.builder.emit_reg(Op::Return, result); 1341 - let inner_func = inner.builder.finish(); 2218 + let mut inner_func = inner.builder.finish(); 2219 + inner_func.upvalue_defs = upvalue_entries.iter().map(|e| e.def.clone()).collect(); 1342 2220 let func_idx = fc.builder.add_function(inner_func); 1343 2221 fc.builder.emit_reg_u16(Op::CreateClosure, dst, func_idx); 1344 2222 } ··· 1358 2236 }); 1359 2237 if let Some(member) = ctor { 1360 2238 if let ClassMemberKind::Method { value, .. } = &member.kind { 1361 - let inner = compile_function_body(value)?; 2239 + let inner = compile_function_body_with_captures(fc, value)?; 1362 2240 let func_idx = fc.builder.add_function(inner); 1363 2241 fc.builder.emit_reg_u16(Op::CreateClosure, dst, func_idx); 1364 2242 } ··· 1389 2267 PropertyKey::Identifier(s) | PropertyKey::String(s) => s.clone(), 1390 2268 _ => continue, 1391 2269 }; 1392 - let inner = compile_function_body(value)?; 2270 + let inner = compile_function_body_with_captures(fc, value)?; 1393 2271 let func_idx = fc.builder.add_function(inner); 1394 2272 let method_reg = fc.alloc_reg(); 1395 2273 fc.builder ··· 1487 2365 fn compile_store(fc: &mut FunctionCompiler, target: &Expr, src: Reg) -> Result<(), JsError> { 1488 2366 match &target.kind { 1489 2367 ExprKind::Identifier(name) => { 1490 - if let Some(local) = fc.find_local(name) { 1491 - if local != src { 1492 - fc.builder.emit_reg_reg(Op::Move, local, src); 2368 + if let Some(local) = fc.find_local_info(name) { 2369 + if local.is_const { 2370 + return Err(JsError::SyntaxError(format!( 2371 + "Assignment to constant variable '{name}'" 2372 + ))); 1493 2373 } 2374 + let reg = local.reg; 2375 + let captured = local.is_captured; 2376 + if captured { 2377 + fc.builder.emit_reg_reg(Op::CellStore, reg, src); 2378 + } else if reg != src { 2379 + fc.builder.emit_reg_reg(Op::Move, reg, src); 2380 + } 2381 + } else if let Some(uv_idx) = fc.find_upvalue(name) { 2382 + if fc.is_upvalue_const(uv_idx) { 2383 + return Err(JsError::SyntaxError(format!( 2384 + "Assignment to constant variable '{name}'" 2385 + ))); 2386 + } 2387 + fc.builder.emit_store_upvalue(uv_idx, src); 1494 2388 } else { 1495 2389 let ni = fc.builder.add_name(name); 1496 2390 fc.builder.emit_store_global(ni, src);
+312 -11
crates/js/src/vm.rs
··· 12 12 13 13 // ── Heap objects (GC-managed) ──────────────────────────────── 14 14 15 - /// A GC-managed heap object: either a plain object or a function. 15 + /// A GC-managed heap object: a plain object, a function, or a closure cell. 16 16 pub enum HeapObject { 17 17 Object(ObjectData), 18 - Function(FunctionData), 18 + Function(Box<FunctionData>), 19 + /// A mutable cell holding one Value — used for closure-captured variables. 20 + Cell(Value), 19 21 } 20 22 21 23 impl Traceable for HeapObject { ··· 39 41 if let Some(r) = prop.value.gc_ref() { 40 42 visitor(r); 41 43 } 44 + } 45 + for &uv in &fdata.upvalues { 46 + visitor(uv); 47 + } 48 + } 49 + HeapObject::Cell(val) => { 50 + if let Some(r) = val.gc_ref() { 51 + visitor(r); 42 52 } 43 53 } 44 54 } ··· 116 126 pub prototype_obj: Option<GcRef>, 117 127 /// Arbitrary properties set on this function (functions are objects in JS). 118 128 pub properties: HashMap<String, Property>, 129 + /// Captured upvalue cells (GcRefs to HeapObject::Cell values). 130 + pub upvalues: Vec<GcRef>, 119 131 } 120 132 121 133 #[derive(Clone)] ··· 631 643 return_reg: usize, 632 644 /// Exception handler stack for this frame. 633 645 exception_handlers: Vec<ExceptionHandler>, 646 + /// Captured upvalue cells from the closure that created this call frame. 647 + upvalues: Vec<GcRef>, 634 648 } 635 649 636 650 /// An exception handler entry (for try/catch). ··· 694 708 base: 0, 695 709 return_reg: 0, 696 710 exception_handlers: Vec::new(), 711 + upvalues: Vec::new(), 697 712 }); 698 713 699 714 self.run() ··· 752 767 roots.push(r); 753 768 } 754 769 } 770 + for frame in &self.frames { 771 + for &uv in &frame.upvalues { 772 + roots.push(uv); 773 + } 774 + } 755 775 roots 756 776 } 757 777 ··· 1140 1160 match self.gc.get(func_gc_ref) { 1141 1161 Some(HeapObject::Function(fdata)) => match &fdata.kind { 1142 1162 FunctionKind::Native(n) => CallInfo::Native(n.callback), 1143 - FunctionKind::Bytecode(bc) => CallInfo::Bytecode(bc.func.clone()), 1163 + FunctionKind::Bytecode(bc) => { 1164 + CallInfo::Bytecode(bc.func.clone(), fdata.upvalues.clone()) 1165 + } 1144 1166 }, 1145 1167 _ => { 1146 1168 let err = RuntimeError::type_error("not a function"); ··· 1165 1187 } 1166 1188 } 1167 1189 }, 1168 - CallInfo::Bytecode(callee_func) => { 1190 + CallInfo::Bytecode(callee_func, callee_upvalues) => { 1169 1191 if self.frames.len() >= MAX_CALL_DEPTH { 1170 1192 let err = 1171 1193 RuntimeError::range_error("Maximum call stack size exceeded"); ··· 1195 1217 base: callee_base, 1196 1218 return_reg: base + dst as usize, 1197 1219 exception_handlers: Vec::new(), 1220 + upvalues: callee_upvalues, 1198 1221 }); 1199 1222 } 1200 1223 } ··· 1231 1254 let base = self.frames[fi].base; 1232 1255 let inner_func = self.frames[fi].func.functions[func_idx].clone(); 1233 1256 let name = inner_func.name.clone(); 1257 + 1258 + // Resolve upvalues from the parent scope. 1259 + let mut upvalues = Vec::with_capacity(inner_func.upvalue_defs.len()); 1260 + for def in &inner_func.upvalue_defs { 1261 + let cell_ref = if def.is_local { 1262 + // Parent has a cell in register `def.index`. 1263 + match &self.registers[base + def.index as usize] { 1264 + Value::Object(r) => *r, 1265 + _ => { 1266 + return Err(RuntimeError { 1267 + kind: ErrorKind::Error, 1268 + message: 1269 + "CreateClosure: upvalue register does not hold a cell" 1270 + .into(), 1271 + }); 1272 + } 1273 + } 1274 + } else { 1275 + // Transitive: parent's own upvalue at `def.index`. 1276 + self.frames[fi].upvalues[def.index as usize] 1277 + }; 1278 + upvalues.push(cell_ref); 1279 + } 1280 + 1234 1281 // Create a .prototype object for the function (for instanceof). 1235 1282 let proto_obj = self.gc.alloc(HeapObject::Object(ObjectData::new())); 1236 - let gc_ref = self.gc.alloc(HeapObject::Function(FunctionData { 1283 + let gc_ref = self.gc.alloc(HeapObject::Function(Box::new(FunctionData { 1237 1284 name, 1238 1285 kind: FunctionKind::Bytecode(BytecodeFunc { func: inner_func }), 1239 1286 prototype_obj: Some(proto_obj), 1240 1287 properties: HashMap::new(), 1241 - })); 1288 + upvalues, 1289 + }))); 1242 1290 // Set .prototype.constructor = this function. 1243 1291 if let Some(HeapObject::Object(data)) = self.gc.get_mut(proto_obj) { 1244 1292 data.properties.insert( ··· 1512 1560 Op::PopExceptionHandler => { 1513 1561 self.frames[fi].exception_handlers.pop(); 1514 1562 } 1563 + 1564 + // ── Closure / upvalue ops ───────────────────────── 1565 + Op::NewCell => { 1566 + let dst = Self::read_u8(&mut self.frames[fi]); 1567 + let base = self.frames[fi].base; 1568 + let cell = self.gc.alloc(HeapObject::Cell(Value::Undefined)); 1569 + self.registers[base + dst as usize] = Value::Object(cell); 1570 + 1571 + if self.gc.should_collect() { 1572 + let roots = self.collect_roots(); 1573 + self.gc.collect(&roots); 1574 + } 1575 + } 1576 + Op::CellLoad => { 1577 + let dst = Self::read_u8(&mut self.frames[fi]); 1578 + let cell_reg = Self::read_u8(&mut self.frames[fi]); 1579 + let base = self.frames[fi].base; 1580 + let cell_ref = match &self.registers[base + cell_reg as usize] { 1581 + Value::Object(r) => *r, 1582 + _ => { 1583 + return Err(RuntimeError { 1584 + kind: ErrorKind::Error, 1585 + message: "CellLoad: register does not hold a cell".into(), 1586 + }); 1587 + } 1588 + }; 1589 + let val = match self.gc.get(cell_ref) { 1590 + Some(HeapObject::Cell(v)) => v.clone(), 1591 + _ => Value::Undefined, 1592 + }; 1593 + self.registers[base + dst as usize] = val; 1594 + } 1595 + Op::CellStore => { 1596 + let cell_reg = Self::read_u8(&mut self.frames[fi]); 1597 + let src = Self::read_u8(&mut self.frames[fi]); 1598 + let base = self.frames[fi].base; 1599 + let cell_ref = match &self.registers[base + cell_reg as usize] { 1600 + Value::Object(r) => *r, 1601 + _ => { 1602 + return Err(RuntimeError { 1603 + kind: ErrorKind::Error, 1604 + message: "CellStore: register does not hold a cell".into(), 1605 + }); 1606 + } 1607 + }; 1608 + let val = self.registers[base + src as usize].clone(); 1609 + if let Some(HeapObject::Cell(cell_val)) = self.gc.get_mut(cell_ref) { 1610 + *cell_val = val; 1611 + } 1612 + } 1613 + Op::LoadUpvalue => { 1614 + let dst = Self::read_u8(&mut self.frames[fi]); 1615 + let idx = Self::read_u8(&mut self.frames[fi]) as usize; 1616 + let base = self.frames[fi].base; 1617 + let cell_ref = self.frames[fi].upvalues[idx]; 1618 + let val = match self.gc.get(cell_ref) { 1619 + Some(HeapObject::Cell(v)) => v.clone(), 1620 + _ => Value::Undefined, 1621 + }; 1622 + self.registers[base + dst as usize] = val; 1623 + } 1624 + Op::StoreUpvalue => { 1625 + let idx = Self::read_u8(&mut self.frames[fi]) as usize; 1626 + let src = Self::read_u8(&mut self.frames[fi]); 1627 + let base = self.frames[fi].base; 1628 + let val = self.registers[base + src as usize].clone(); 1629 + let cell_ref = self.frames[fi].upvalues[idx]; 1630 + if let Some(HeapObject::Cell(cell_val)) = self.gc.get_mut(cell_ref) { 1631 + *cell_val = val; 1632 + } 1633 + } 1515 1634 } 1516 1635 } 1517 1636 } ··· 1549 1668 name: &str, 1550 1669 callback: fn(&[Value]) -> Result<Value, RuntimeError>, 1551 1670 ) { 1552 - let gc_ref = self.gc.alloc(HeapObject::Function(FunctionData { 1671 + let gc_ref = self.gc.alloc(HeapObject::Function(Box::new(FunctionData { 1553 1672 name: name.to_string(), 1554 1673 kind: FunctionKind::Native(NativeFunc { callback }), 1555 1674 prototype_obj: None, 1556 1675 properties: HashMap::new(), 1557 - })); 1676 + upvalues: Vec::new(), 1677 + }))); 1558 1678 self.globals 1559 1679 .insert(name.to_string(), Value::Function(gc_ref)); 1560 1680 } ··· 1579 1699 /// Internal enum to avoid holding a GC borrow across the call setup. 1580 1700 enum CallInfo { 1581 1701 Native(fn(&[Value]) -> Result<Value, RuntimeError>), 1582 - Bytecode(Function), 1702 + Bytecode(Function, Vec<GcRef>), 1583 1703 } 1584 1704 1585 1705 // ── Tests ──────────────────────────────────────────────────── ··· 2400 2520 2401 2521 // Create a constructor function with a .prototype object. 2402 2522 let proto = gc.alloc(HeapObject::Object(ObjectData::new())); 2403 - let ctor = gc.alloc(HeapObject::Function(FunctionData { 2523 + let ctor = gc.alloc(HeapObject::Function(Box::new(FunctionData { 2404 2524 name: "Foo".to_string(), 2405 2525 kind: FunctionKind::Native(NativeFunc { 2406 2526 callback: |_| Ok(Value::Undefined), 2407 2527 }), 2408 2528 prototype_obj: Some(proto), 2409 2529 properties: HashMap::new(), 2410 - })); 2530 + upvalues: Vec::new(), 2531 + }))); 2411 2532 2412 2533 // Create an object whose [[Prototype]] is the constructor's .prototype. 2413 2534 let mut obj_data = ObjectData::new(); ··· 2447 2568 match eval(src).unwrap() { 2448 2569 Value::Boolean(true) => {} 2449 2570 v => panic!("expected true, got {v:?}"), 2571 + } 2572 + } 2573 + 2574 + // ── Closure tests ──────────────────────────────────────── 2575 + 2576 + #[test] 2577 + fn test_closure_basic() { 2578 + // Basic closure: inner function reads outer variable. 2579 + let src = r#" 2580 + function outer() { 2581 + var x = 10; 2582 + function inner() { 2583 + return x; 2584 + } 2585 + return inner(); 2586 + } 2587 + outer() 2588 + "#; 2589 + match eval(src).unwrap() { 2590 + Value::Number(n) => assert_eq!(n, 10.0), 2591 + v => panic!("expected 10, got {v:?}"), 2592 + } 2593 + } 2594 + 2595 + #[test] 2596 + fn test_closure_return_function() { 2597 + // Closure survives the outer function's return. 2598 + let src = r#" 2599 + function makeAdder(x) { 2600 + return function(y) { return x + y; }; 2601 + } 2602 + var add5 = makeAdder(5); 2603 + add5(3) 2604 + "#; 2605 + match eval(src).unwrap() { 2606 + Value::Number(n) => assert_eq!(n, 8.0), 2607 + v => panic!("expected 8, got {v:?}"), 2608 + } 2609 + } 2610 + 2611 + #[test] 2612 + fn test_closure_mutation() { 2613 + // Closures share live references — mutation is visible. 2614 + let src = r#" 2615 + function counter() { 2616 + var n = 0; 2617 + return function() { n = n + 1; return n; }; 2618 + } 2619 + var c = counter(); 2620 + c(); 2621 + c(); 2622 + c() 2623 + "#; 2624 + match eval(src).unwrap() { 2625 + Value::Number(n) => assert_eq!(n, 3.0), 2626 + v => panic!("expected 3, got {v:?}"), 2627 + } 2628 + } 2629 + 2630 + #[test] 2631 + fn test_closure_shared_variable() { 2632 + // Two closures from the same scope share the same variable. 2633 + let src = r#" 2634 + function make() { 2635 + var x = 0; 2636 + function inc() { x = x + 1; } 2637 + function get() { return x; } 2638 + inc(); 2639 + inc(); 2640 + return get(); 2641 + } 2642 + make() 2643 + "#; 2644 + match eval(src).unwrap() { 2645 + Value::Number(n) => assert_eq!(n, 2.0), 2646 + v => panic!("expected 2, got {v:?}"), 2647 + } 2648 + } 2649 + 2650 + #[test] 2651 + fn test_closure_arrow() { 2652 + // Arrow function captures outer variable. 2653 + let src = r#" 2654 + function outer() { 2655 + var x = 42; 2656 + var f = () => x; 2657 + return f(); 2658 + } 2659 + outer() 2660 + "#; 2661 + match eval(src).unwrap() { 2662 + Value::Number(n) => assert_eq!(n, 42.0), 2663 + v => panic!("expected 42, got {v:?}"), 2664 + } 2665 + } 2666 + 2667 + #[test] 2668 + fn test_closure_nested() { 2669 + // Transitive capture: grandchild function reads grandparent variable. 2670 + let src = r#" 2671 + function outer() { 2672 + var x = 100; 2673 + function middle() { 2674 + function inner() { 2675 + return x; 2676 + } 2677 + return inner(); 2678 + } 2679 + return middle(); 2680 + } 2681 + outer() 2682 + "#; 2683 + match eval(src).unwrap() { 2684 + Value::Number(n) => assert_eq!(n, 100.0), 2685 + v => panic!("expected 100, got {v:?}"), 2686 + } 2687 + } 2688 + 2689 + #[test] 2690 + fn test_closure_param_capture() { 2691 + // Closure captures a function parameter. 2692 + let src = r#" 2693 + function multiply(factor) { 2694 + return function(x) { return x * factor; }; 2695 + } 2696 + var double = multiply(2); 2697 + double(7) 2698 + "#; 2699 + match eval(src).unwrap() { 2700 + Value::Number(n) => assert_eq!(n, 14.0), 2701 + v => panic!("expected 14, got {v:?}"), 2702 + } 2703 + } 2704 + 2705 + // ── const tests ────────────────────────────────────────── 2706 + 2707 + #[test] 2708 + fn test_const_basic() { 2709 + let src = "const x = 42; x"; 2710 + match eval(src).unwrap() { 2711 + Value::Number(n) => assert_eq!(n, 42.0), 2712 + v => panic!("expected 42, got {v:?}"), 2713 + } 2714 + } 2715 + 2716 + #[test] 2717 + fn test_const_reassignment_error() { 2718 + let src = "const x = 1; x = 2;"; 2719 + let program = crate::parser::Parser::parse(src).expect("parse ok"); 2720 + let result = crate::compiler::compile(&program); 2721 + assert!( 2722 + result.is_err(), 2723 + "const reassignment should be a compile error" 2724 + ); 2725 + } 2726 + 2727 + #[test] 2728 + fn test_const_missing_init_error() { 2729 + let src = "const x;"; 2730 + let program = crate::parser::Parser::parse(src).expect("parse ok"); 2731 + let result = crate::compiler::compile(&program); 2732 + assert!( 2733 + result.is_err(), 2734 + "const without initializer should be a compile error" 2735 + ); 2736 + } 2737 + 2738 + // ── this binding tests ─────────────────────────────────── 2739 + 2740 + #[test] 2741 + fn test_method_call_this() { 2742 + let src = r#" 2743 + var obj = {}; 2744 + obj.x = 10; 2745 + obj.getX = function() { return this.x; }; 2746 + obj.getX() 2747 + "#; 2748 + match eval(src).unwrap() { 2749 + Value::Number(n) => assert_eq!(n, 10.0), 2750 + v => panic!("expected 10, got {v:?}"), 2450 2751 } 2451 2752 } 2452 2753 }