diff --git a/Cargo.lock b/Cargo.lock index a96cd79..07bd054 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1120,12 +1120,15 @@ name = "oxide_compiler" version = "0.1.0" dependencies = [ "oxide_bytecode", + "oxide_cfg", "oxide_code_cache", "oxide_dce", "oxide_emit", "oxide_ir", + "oxide_liveness", "oxide_log", "oxide_parser", + "oxide_regalloc", "rustc-hash", "serde", "serde_json", @@ -1214,6 +1217,7 @@ version = "0.1.0" dependencies = [ "oxide_bytecode", "oxide_cfg", + "oxide_dce", "oxide_emit", "oxide_ir", "oxide_liveness", diff --git a/crates/oxide_emit/src/emit.rs b/crates/oxide_emit/src/emit.rs index 31f2960..957a8f8 100644 --- a/crates/oxide_emit/src/emit.rs +++ b/crates/oxide_emit/src/emit.rs @@ -750,6 +750,17 @@ impl Emitter { ctx.own_bindings = self.collect_own_binding_names(¶m_names, body_stmts); ctx.captured_bindings = self.collect_captured_bindings(body_stmts, &ctx.own_bindings); + // 被捕获的参数也必须建 cell(MAKE_CELL):否则子函数经 lazy upvalue 路径读 + // 自身寄存器(依赖调用者寄存器残留),vreg 化/RegAlloc 移动寄存器后读到垃圾。 + // 与 var/let/const 的 MAKE_CELL 语义一致(binding.rs:50)。 + for spec in param_specs { + let name = spec.register_name(); + if let Some(&cell_idx) = ctx.captured_bindings.get(name) { + let reg = ctx.lookup(name)?; + ctx.inst(Inst::new(OpCode::MAKE_CELL, Operand::Reg(reg), Operand::Imm(cell_idx as u16), Operand::None)); + } + } + // Free variable analysis for upvalue capture (Ordinary + Arrow functions only) if matches!(body_context, FunctionBodyContext::Ordinary | FunctionBodyContext::Arrow) { ctx.current_upvalue_captures = diff --git a/crates/oxide_emit/src/expr/call.rs b/crates/oxide_emit/src/expr/call.rs index 7fdf0f4..073f564 100644 --- a/crates/oxide_emit/src/expr/call.rs +++ b/crates/oxide_emit/src/expr/call.rs @@ -20,7 +20,7 @@ impl Emitter { arg_regs.push(self.emit_expression(expr, ctx)?); } } - let first_arg_reg = if arg_regs.is_empty() { 0u32 } else { arg_regs[0] }; + let first_arg_reg = if arg_regs.is_empty() { 0u32 } else { pack_arg_regs(&mut arg_regs, ctx) }; let result_reg = ctx.alloc_reg(); ctx.inst(Inst::super_call( Operand::Reg(result_reg), @@ -88,7 +88,7 @@ impl Emitter { arg_regs.push(self.emit_expression(expr, ctx)?); } } - let first_arg_reg = if arg_regs.is_empty() { 0u32 } else { arg_regs[0] }; + let first_arg_reg = if arg_regs.is_empty() { 0u32 } else { pack_arg_regs(&mut arg_regs, ctx) }; let op = match &call.callee { Expression::Identifier(ident) if ctx.is_builtin(ident.name.as_str()) => OpCode::CALL_NATIVE, _ => OpCode::CALL, @@ -124,3 +124,23 @@ impl Emitter { } } } + +/// 把调用参数打包到连续寄存器块(VM 按 `regs[first_arg + i]` 连续读参数,D-17/调用契约)。 +/// vreg 化(05-02)后各参数由独立 vreg 承载,复杂表达式(对象/数组字面量、嵌套调用) +/// 的临时寄存器会使参数 vreg 不连续——这里检测到不连续时用 MOV 打包到新连续块。 +/// 返回首参寄存器;参数为空时返回 0。 +fn pack_arg_regs(arg_regs: &mut [u32], ctx: &mut CompileCtx) -> u32 { + let consecutive = arg_regs.windows(2).all(|w| w[1] == w[0] + 1); + if consecutive { + return arg_regs[0]; + } + // 预留连续块(单调 alloc_reg 保证 base..base+n 连续) + let base = ctx.alloc_reg(); + for _ in 1..arg_regs.len() { + ctx.alloc_reg(); + } + for (i, ®) in arg_regs.iter().enumerate() { + ctx.inst(Inst::inst_mov(Operand::Reg(base + i as u32), Operand::Reg(reg))); + } + base +} diff --git a/crates/oxide_regalloc/src/rewrite.rs b/crates/oxide_regalloc/src/rewrite.rs index 8b7c596..3d34e21 100644 --- a/crates/oxide_regalloc/src/rewrite.rs +++ b/crates/oxide_regalloc/src/rewrite.rs @@ -193,9 +193,31 @@ fn rewrite_inst( } }; - let mut rd = rewrite(inst.rd); + let rd = rewrite(inst.rd); let mut a = rewrite(inst.a); let mut b = rewrite(inst.b); + // TEMPLATE_STR 的 ext 编码表达式寄存器(seg>>31==1 时低 8 位为 expr_reg)——必须随 + // RegAlloc 重映射,否则读旧 vreg 号对应的物理槽(错值)。 + let ext = if inst.op == OpCode::TEMPLATE_STR { + let mut ext = inst.ext.clone(); + for seg in ext.iter_mut().skip(1) { + if *seg >> 31 == 1 { + let r = *seg & 0xFF; + let nr = if let Some(&c) = slot_color.get(&r) { + c + } else { + match map.map.get(&r) { + Some(Alloc::Phys(p)) => *p, + _ => r, // 非真实 vreg / spilled 未覆盖(防御保留原号) + } + }; + *seg = (*seg & !0xFFu32) | (nr & 0xFF); + } + } + ext + } else { + inst.ext.clone() + }; // 调用点首参槽改指 arg_window_base(桥接后) if let Some(ab) = arg_base { match inst.op { @@ -212,11 +234,8 @@ fn rewrite_inst( _ => {} } } - let _ = &mut rd; - let _ = &mut a; - let _ = &mut b; let _ = f; - (Inst { op: inst.op, rd, a, b, ext: inst.ext.clone() }, arg_movs) + (Inst { op: inst.op, rd, a, b, ext }, arg_movs) } /// terminator 判定:跳转族 + RETURN/HALT/THROW(def 结果必死,SPILL 跳过)。 diff --git a/crates/oxide_test262/src/main.rs b/crates/oxide_test262/src/main.rs index aaf8530..6400db9 100644 --- a/crates/oxide_test262/src/main.rs +++ b/crates/oxide_test262/src/main.rs @@ -168,6 +168,10 @@ struct RunConfig { supervise: bool, leak_check: bool, leak_check_interval: usize, + /// D-18:关闭 liveness/精确 DCE/RegAlloc 链(on/off 对比基础设施)。 + no_regalloc: bool, + /// 逐测试打印 PASS/FAIL/SKIP(on/off 结果集合对比用)。 + verbose: bool, } /// 内嵌的 test262 harness 辅助脚本注册表(编译期 include_str! 打包)。 @@ -322,6 +326,8 @@ impl RunConfig { supervise: false, leak_check: false, leak_check_interval: 1000, + no_regalloc: false, + verbose: false, } } @@ -333,6 +339,8 @@ impl RunConfig { for arg in args.iter().skip(1) { match arg.as_str() { "--no-skip" => config.no_skip = true, + "--no-regalloc" => config.no_regalloc = true, + "--verbose" => config.verbose = true, "--supervise" => config.supervise = true, "--leak-check" => config.leak_check = true, "--help" | "-h" => return Err(Self::usage()), @@ -360,9 +368,11 @@ impl RunConfig { /// 打印用法说明。 fn usage() -> String { - "usage: test262-runner [--no-skip] [--supervise] [--leak-check] [--leak-check-interval=N] [test262-root] [path-filter]\n\ + "usage: test262-runner [--no-skip] [--no-regalloc] [--verbose] [--supervise] [--leak-check] [--leak-check-interval=N] [test262-root] [path-filter]\n\ \n\ --no-skip Run capability-excluded tests and count unsupported compile/runtime results as failures.\n\ + --no-regalloc Disable the liveness/precise-DCE/RegAlloc compiler chain (vregs stay as physical numbers).\n\ + --verbose Print one PASS/FAIL/SKIP line per test (for on/off result-set comparison).\n\ --supervise Run the suite as single-worker child-process windows with a hard per-test timeout and\n\ \x20 automatic resume past any hanging/crashing test. A hang or crash is reported by path.\n\ --leak-check Monitor session_object_ptrs, session_bytes, code_forge.len(), symbol_registry.len() every\n\ @@ -423,14 +433,15 @@ fn is_skipped(meta: &TestMeta) -> Option { } /// 在 catch_unwind 保护下运行单个测试,把引擎 panic 记为失败。 +#[expect(clippy::too_many_arguments)] fn run_test( path: &Path, source: &str, meta: &TestMeta, kernel: &Arc, harness: &HarnessSources, - harness_cache: &Arc>, no_skip: bool, + harness_cache: &Arc>, no_skip: bool, no_regalloc: bool, ) -> TestResult { let start = std::time::Instant::now(); let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| { - run_test_inner(path, source, meta, kernel, harness, harness_cache, no_skip) + run_test_inner(path, source, meta, kernel, harness, harness_cache, no_skip, no_regalloc) })); match result { @@ -444,9 +455,10 @@ fn run_test( /// 单测执行主流程:拼 harness 前缀 → parse → compile → run; /// 依据 `negative` 元数据校验期望错误,未实现特性按 no_skip 选择跳过或失败。 +#[expect(clippy::too_many_arguments)] fn run_test_inner( path: &Path, source: &str, meta: &TestMeta, kernel: &Arc, harness: &HarnessSources, - harness_cache: &Arc>, no_skip: bool, + harness_cache: &Arc>, no_skip: bool, no_regalloc: bool, ) -> TestResult { let start = std::time::Instant::now(); @@ -478,7 +490,8 @@ fn run_test_inner( } }; - let module = match Compiler::new().compile(&program) { + let compiler = if no_regalloc { Compiler::new().with_regalloc(false) } else { Compiler::new() }; + let module = match compiler.compile(&program) { Ok(m) => m, Err(e) => { let dur = start.elapsed().as_millis() as u64; @@ -982,8 +995,8 @@ fn run_supervised(args: &[String], skip_until: usize, end_index: usize, no_skip: /// exactly one `TestResult`. Worker-owned state (`kernel`, `harness_sources`, /// `harness_cache`) never crosses a thread boundary. fn process_path( - path: &Path, filter: &Option, no_skip: bool, kernel: &Arc, harness_sources: &HarnessSources, - harness_cache: &Arc>, + path: &Path, filter: &Option, no_skip: bool, no_regalloc: bool, kernel: &Arc, + harness_sources: &HarnessSources, harness_cache: &Arc>, ) -> TestResult { let source = match std::fs::read_to_string(path) { Ok(s) => s, @@ -1016,7 +1029,7 @@ fn process_path( } } - run_test(path, &source, &meta, kernel, harness_sources, harness_cache, no_skip) + run_test(path, &source, &meta, kernel, harness_sources, harness_cache, no_skip, no_regalloc) } /// Build a runner kernel with a bounded step limit. Each parallel worker owns @@ -1242,6 +1255,8 @@ fn run_tests() -> bool { let progress = AtomicUsize::new(skip_until); let filter = &filter; let no_skip = config.no_skip; + let no_regalloc = config.no_regalloc; + let verbose = config.verbose; let paths_ref = &paths; let heartbeat_ref = &heartbeat_path; let harness_cache = Arc::new(RwLock::new(HarnessPrefixCache::new())); @@ -1284,9 +1299,18 @@ fn run_tests() -> bool { write_heartbeat(hb, "START", i, stats.pass, stats.fail, stats.skip); } - let result = - process_path(&paths_ref[i], filter, no_skip, &kernel, harness_sources, &harness_cache); + let result = process_path( + &paths_ref[i], filter, no_skip, no_regalloc, &kernel, harness_sources, &harness_cache, + ); stats.record(&result); + if verbose { + let tag = match &result.outcome { + TestOutcome::Pass(_) => "PASS", + TestOutcome::Fail(_) => "FAIL", + TestOutcome::Skip(_) => "SKIP", + }; + println!("{tag} {}", paths_ref[i].display()); + } tests_since_kernel_reset += 1; let done = progress.fetch_add(1, Ordering::Relaxed) + 1;