mirror of
https://github.com/pjh456/OxideJS.git
synced 2026-10-23 18:21:13 +08:00
fix: 调用参数打包连续寄存器 + 捕获参数建 cell + TEMPLATE_STR ext 重映射(修复 JSON/closures/template 回归)
This commit is contained in:
Generated
+4
@@ -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",
|
||||
|
||||
@@ -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 =
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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 跳过)。
|
||||
|
||||
@@ -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<String> {
|
||||
}
|
||||
|
||||
/// 在 catch_unwind 保护下运行单个测试,把引擎 panic 记为失败。
|
||||
#[expect(clippy::too_many_arguments)]
|
||||
fn run_test(
|
||||
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 {
|
||||
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<KernelCore>, harness: &HarnessSources,
|
||||
harness_cache: &Arc<RwLock<HarnessPrefixCache>>, no_skip: bool,
|
||||
harness_cache: &Arc<RwLock<HarnessPrefixCache>>, 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<String>, no_skip: bool, kernel: &Arc<KernelCore>, harness_sources: &HarnessSources,
|
||||
harness_cache: &Arc<RwLock<HarnessPrefixCache>>,
|
||||
path: &Path, filter: &Option<String>, no_skip: bool, no_regalloc: bool, kernel: &Arc<KernelCore>,
|
||||
harness_sources: &HarnessSources, harness_cache: &Arc<RwLock<HarnessPrefixCache>>,
|
||||
) -> 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;
|
||||
|
||||
Reference in New Issue
Block a user