fix(emit): 调用参数打包连续寄存器 + 捕获参数建 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"
|
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",
|
||||||
|
|||||||
@@ -750,6 +750,17 @@ impl Emitter {
|
|||||||
ctx.own_bindings = self.collect_own_binding_names(¶m_names, body_stmts);
|
ctx.own_bindings = self.collect_own_binding_names(¶m_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 =
|
||||||
|
|||||||
@@ -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, ®) 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 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 跳过)。
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
Reference in New Issue
Block a user