fix(emit): 调用参数打包连续寄存器 + 捕获参数建 cell + TEMPLATE_STR ext 重映射(修复 JSON/closures/template 回归)

This commit is contained in:
爱特每羊队
2026-08-06 19:33:05 +08:00
parent a763e7ab75
commit 8317c03edb
5 changed files with 95 additions and 17 deletions
Generated
+4
View File
@@ -1120,12 +1120,15 @@ name = "oxide_compiler"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"oxide_bytecode", "oxide_bytecode",
"oxide_cfg",
"oxide_code_cache", "oxide_code_cache",
"oxide_dce", "oxide_dce",
"oxide_emit", "oxide_emit",
"oxide_ir", "oxide_ir",
"oxide_liveness",
"oxide_log", "oxide_log",
"oxide_parser", "oxide_parser",
"oxide_regalloc",
"rustc-hash", "rustc-hash",
"serde", "serde",
"serde_json", "serde_json",
@@ -1214,6 +1217,7 @@ version = "0.1.0"
dependencies = [ dependencies = [
"oxide_bytecode", "oxide_bytecode",
"oxide_cfg", "oxide_cfg",
"oxide_dce",
"oxide_emit", "oxide_emit",
"oxide_ir", "oxide_ir",
"oxide_liveness", "oxide_liveness",
+11
View File
@@ -750,6 +750,17 @@ impl Emitter {
ctx.own_bindings = self.collect_own_binding_names(&param_names, body_stmts); ctx.own_bindings = self.collect_own_binding_names(&param_names, body_stmts);
ctx.captured_bindings = self.collect_captured_bindings(body_stmts, &ctx.own_bindings); 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) // Free variable analysis for upvalue capture (Ordinary + Arrow functions only)
if matches!(body_context, FunctionBodyContext::Ordinary | FunctionBodyContext::Arrow) { if matches!(body_context, FunctionBodyContext::Ordinary | FunctionBodyContext::Arrow) {
ctx.current_upvalue_captures = ctx.current_upvalue_captures =
+22 -2
View File
@@ -20,7 +20,7 @@ impl Emitter {
arg_regs.push(self.emit_expression(expr, ctx)?); 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(); let result_reg = ctx.alloc_reg();
ctx.inst(Inst::super_call( ctx.inst(Inst::super_call(
Operand::Reg(result_reg), Operand::Reg(result_reg),
@@ -88,7 +88,7 @@ impl Emitter {
arg_regs.push(self.emit_expression(expr, ctx)?); 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 { let op = match &call.callee {
Expression::Identifier(ident) if ctx.is_builtin(ident.name.as_str()) => OpCode::CALL_NATIVE, Expression::Identifier(ident) if ctx.is_builtin(ident.name.as_str()) => OpCode::CALL_NATIVE,
_ => OpCode::CALL, _ => 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, &reg) in arg_regs.iter().enumerate() {
ctx.inst(Inst::inst_mov(Operand::Reg(base + i as u32), Operand::Reg(reg)));
}
base
}
+24 -5
View File
@@ -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 a = rewrite(inst.a);
let mut b = rewrite(inst.b); 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(桥接后) // 调用点首参槽改指 arg_window_base(桥接后)
if let Some(ab) = arg_base { if let Some(ab) = arg_base {
match inst.op { match inst.op {
@@ -212,11 +234,8 @@ fn rewrite_inst(
_ => {} _ => {}
} }
} }
let _ = &mut rd;
let _ = &mut a;
let _ = &mut b;
let _ = f; 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 跳过)。 /// terminator 判定:跳转族 + RETURN/HALT/THROW(def 结果必死,SPILL 跳过)。
+34 -10
View File
@@ -168,6 +168,10 @@ struct RunConfig {
supervise: bool, supervise: bool,
leak_check: bool, leak_check: bool,
leak_check_interval: usize, 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! 打包)。 /// 内嵌的 test262 harness 辅助脚本注册表(编译期 include_str! 打包)。
@@ -322,6 +326,8 @@ impl RunConfig {
supervise: false, supervise: false,
leak_check: false, leak_check: false,
leak_check_interval: 1000, leak_check_interval: 1000,
no_regalloc: false,
verbose: false,
} }
} }
@@ -333,6 +339,8 @@ impl RunConfig {
for arg in args.iter().skip(1) { for arg in args.iter().skip(1) {
match arg.as_str() { match arg.as_str() {
"--no-skip" => config.no_skip = true, "--no-skip" => config.no_skip = true,
"--no-regalloc" => config.no_regalloc = true,
"--verbose" => config.verbose = true,
"--supervise" => config.supervise = true, "--supervise" => config.supervise = true,
"--leak-check" => config.leak_check = true, "--leak-check" => config.leak_check = true,
"--help" | "-h" => return Err(Self::usage()), "--help" | "-h" => return Err(Self::usage()),
@@ -360,9 +368,11 @@ impl RunConfig {
/// 打印用法说明。 /// 打印用法说明。
fn usage() -> String { 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\ \n\
--no-skip Run capability-excluded tests and count unsupported compile/runtime results as failures.\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\ --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\ \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\ --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<String> {
} }
/// 在 catch_unwind 保护下运行单个测试,把引擎 panic 记为失败。 /// 在 catch_unwind 保护下运行单个测试,把引擎 panic 记为失败。
#[expect(clippy::too_many_arguments)]
fn run_test( fn run_test(
path: &Path, source: &str, meta: &TestMeta, kernel: &Arc<KernelCore>, harness: &HarnessSources, path: &Path, source: &str, meta: &TestMeta, kernel: &Arc<KernelCore>, harness: &HarnessSources,
harness_cache: &Arc<RwLock<HarnessPrefixCache>>, no_skip: bool, harness_cache: &Arc<RwLock<HarnessPrefixCache>>, no_skip: bool, no_regalloc: bool,
) -> TestResult { ) -> TestResult {
let start = std::time::Instant::now(); let start = std::time::Instant::now();
let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| { 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 { match result {
@@ -444,9 +455,10 @@ fn run_test(
/// 单测执行主流程:拼 harness 前缀 → parse → compile → run; /// 单测执行主流程:拼 harness 前缀 → parse → compile → run;
/// 依据 `negative` 元数据校验期望错误,未实现特性按 no_skip 选择跳过或失败。 /// 依据 `negative` 元数据校验期望错误,未实现特性按 no_skip 选择跳过或失败。
#[expect(clippy::too_many_arguments)]
fn run_test_inner( fn run_test_inner(
path: &Path, source: &str, meta: &TestMeta, kernel: &Arc<KernelCore>, harness: &HarnessSources, path: &Path, source: &str, meta: &TestMeta, kernel: &Arc<KernelCore>, harness: &HarnessSources,
harness_cache: &Arc<RwLock<HarnessPrefixCache>>, no_skip: bool, harness_cache: &Arc<RwLock<HarnessPrefixCache>>, no_skip: bool, no_regalloc: bool,
) -> TestResult { ) -> TestResult {
let start = std::time::Instant::now(); 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, Ok(m) => m,
Err(e) => { Err(e) => {
let dur = start.elapsed().as_millis() as u64; 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`, /// exactly one `TestResult`. Worker-owned state (`kernel`, `harness_sources`,
/// `harness_cache`) never crosses a thread boundary. /// `harness_cache`) never crosses a thread boundary.
fn process_path( fn process_path(
path: &Path, filter: &Option<String>, no_skip: bool, kernel: &Arc<KernelCore>, harness_sources: &HarnessSources, path: &Path, filter: &Option<String>, no_skip: bool, no_regalloc: bool, kernel: &Arc<KernelCore>,
harness_cache: &Arc<RwLock<HarnessPrefixCache>>, harness_sources: &HarnessSources, harness_cache: &Arc<RwLock<HarnessPrefixCache>>,
) -> TestResult { ) -> TestResult {
let source = match std::fs::read_to_string(path) { let source = match std::fs::read_to_string(path) {
Ok(s) => s, 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 /// 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 progress = AtomicUsize::new(skip_until);
let filter = &filter; let filter = &filter;
let no_skip = config.no_skip; let no_skip = config.no_skip;
let no_regalloc = config.no_regalloc;
let verbose = config.verbose;
let paths_ref = &paths; let paths_ref = &paths;
let heartbeat_ref = &heartbeat_path; let heartbeat_ref = &heartbeat_path;
let harness_cache = Arc::new(RwLock::new(HarnessPrefixCache::new())); 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); write_heartbeat(hb, "START", i, stats.pass, stats.fail, stats.skip);
} }
let result = let result = process_path(
process_path(&paths_ref[i], filter, no_skip, &kernel, harness_sources, &harness_cache); &paths_ref[i], filter, no_skip, no_regalloc, &kernel, harness_sources, &harness_cache,
);
stats.record(&result); 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; tests_since_kernel_reset += 1;
let done = progress.fetch_add(1, Ordering::Relaxed) + 1; let done = progress.fetch_add(1, Ordering::Relaxed) + 1;