From 10f97100cd6b7dc74b9dbed7702493dabc235c60 Mon Sep 17 00:00:00 2001 From: pjh456 <147148383@qq.com> Date: Thu, 8 Oct 2026 06:26:01 +0000 Subject: [PATCH] =?UTF-8?q?refactor:=20=E6=96=B0=E5=BB=BA=20oxide=5Fengine?= =?UTF-8?q?=20crate=20=E7=BB=9F=E4=B8=80=E4=B8=A4=E5=85=A5=E5=8F=A3=20comp?= =?UTF-8?q?ile-run=20=E8=A3=85=E9=85=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Cargo.lock | 50 +++++---- Cargo.toml | 3 +- crates/oxide_cli/Cargo.toml | 1 + crates/oxide_cli/src/bench/mod.rs | 29 ++--- crates/oxide_cli/src/main.rs | 123 ++++++--------------- crates/oxide_engine/Cargo.toml | 15 +++ crates/oxide_engine/src/lib.rs | 168 +++++++++++++++++++++++++++++ crates/oxide_test262/Cargo.toml | 1 + crates/oxide_test262/src/main.rs | 14 +-- crates/oxide_test262/src/runner.rs | 78 +++++++------- 10 files changed, 317 insertions(+), 165 deletions(-) create mode 100644 crates/oxide_engine/Cargo.toml create mode 100644 crates/oxide_engine/src/lib.rs diff --git a/Cargo.lock b/Cargo.lock index 244d26b3..95ddc702 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1160,7 +1160,7 @@ dependencies = [ [[package]] name = "oxide_builtins" -version = "0.8.69" +version = "0.8.70" dependencies = [ "chrono", "fast-float", @@ -1185,7 +1185,7 @@ dependencies = [ [[package]] name = "oxide_bytecode" -version = "0.8.69" +version = "0.8.70" dependencies = [ "num-bigint", "oxide_log", @@ -1193,7 +1193,7 @@ dependencies = [ [[package]] name = "oxide_cfg" -version = "0.8.69" +version = "0.8.70" dependencies = [ "oxide_bytecode", "oxide_emit", @@ -1204,7 +1204,7 @@ dependencies = [ [[package]] name = "oxide_cli" -version = "0.8.69" +version = "0.8.70" dependencies = [ "ansi_term", "bumpalo", @@ -1213,6 +1213,7 @@ dependencies = [ "oxide_bytecode", "oxide_code_cache", "oxide_compiler", + "oxide_engine", "oxide_kernel", "oxide_log", "oxide_parser", @@ -1225,7 +1226,7 @@ dependencies = [ [[package]] name = "oxide_code_cache" -version = "0.8.69" +version = "0.8.70" dependencies = [ "lru", "oxide_bytecode", @@ -1234,7 +1235,7 @@ dependencies = [ [[package]] name = "oxide_compiler" -version = "0.8.69" +version = "0.8.70" dependencies = [ "oxide_bytecode", "oxide_cfg", @@ -1254,7 +1255,7 @@ dependencies = [ [[package]] name = "oxide_dce" -version = "0.8.69" +version = "0.8.70" dependencies = [ "oxide_bytecode", "oxide_cfg", @@ -1268,7 +1269,7 @@ dependencies = [ [[package]] name = "oxide_emit" -version = "0.8.69" +version = "0.8.70" dependencies = [ "num-bigint", "oxc_allocator", @@ -1281,9 +1282,21 @@ dependencies = [ "regress", ] +[[package]] +name = "oxide_engine" +version = "0.8.70" +dependencies = [ + "oxide_bytecode", + "oxide_compiler", + "oxide_kernel", + "oxide_parser", + "oxide_types", + "oxide_vm", +] + [[package]] name = "oxide_ir" -version = "0.8.69" +version = "0.8.70" dependencies = [ "oxide_bytecode", "oxide_log", @@ -1292,7 +1305,7 @@ dependencies = [ [[package]] name = "oxide_kernel" -version = "0.8.69" +version = "0.8.70" dependencies = [ "bumpalo", "dashmap", @@ -1306,7 +1319,7 @@ dependencies = [ [[package]] name = "oxide_liveness" -version = "0.8.69" +version = "0.8.70" dependencies = [ "oxide_bytecode", "oxide_cfg", @@ -1318,7 +1331,7 @@ dependencies = [ [[package]] name = "oxide_log" -version = "0.8.69" +version = "0.8.70" dependencies = [ "tracing", "tracing-appender", @@ -1327,7 +1340,7 @@ dependencies = [ [[package]] name = "oxide_parser" -version = "0.8.69" +version = "0.8.70" dependencies = [ "oxc_allocator", "oxc_ast", @@ -1342,7 +1355,7 @@ dependencies = [ [[package]] name = "oxide_regalloc" -version = "0.8.69" +version = "0.8.70" dependencies = [ "oxide_bytecode", "oxide_cfg", @@ -1358,7 +1371,7 @@ dependencies = [ [[package]] name = "oxide_runtime_api" -version = "0.8.69" +version = "0.8.70" dependencies = [ "num-bigint", "num-traits", @@ -1371,11 +1384,12 @@ dependencies = [ [[package]] name = "oxide_test262" -version = "0.8.69" +version = "0.8.70" dependencies = [ "oxide_bytecode", "oxide_compiler", "oxide_emit", + "oxide_engine", "oxide_kernel", "oxide_log", "oxide_parser", @@ -1388,7 +1402,7 @@ dependencies = [ [[package]] name = "oxide_types" -version = "0.8.69" +version = "0.8.70" dependencies = [ "bumpalo", "dashmap", @@ -1401,7 +1415,7 @@ dependencies = [ [[package]] name = "oxide_vm" -version = "0.8.69" +version = "0.8.70" dependencies = [ "bumpalo", "chrono", diff --git a/Cargo.toml b/Cargo.toml index 525018ed..e468f06b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,12 +17,13 @@ members = [ "crates/oxide_kernel", "crates/oxide_runtime_api", "crates/oxide_builtins", + "crates/oxide_engine", "crates/oxide_cli", "crates/oxide_test262", ] [workspace.package] -version = "0.8.69" +version = "0.8.70" edition = "2021" license = "MIT" rust-version = "1.80" diff --git a/crates/oxide_cli/Cargo.toml b/crates/oxide_cli/Cargo.toml index 584aa1d3..b387151b 100644 --- a/crates/oxide_cli/Cargo.toml +++ b/crates/oxide_cli/Cargo.toml @@ -18,6 +18,7 @@ oxide_types = { path = "../oxide_types" } oxide_compiler = { path = "../oxide_compiler" } oxide_vm = { path = "../oxide_vm" } oxide_kernel = { path = "../oxide_kernel" } +oxide_engine = { path = "../oxide_engine" } oxide_log = { path = "../oxide_log" } bumpalo.workspace = true clap.workspace = true diff --git a/crates/oxide_cli/src/bench/mod.rs b/crates/oxide_cli/src/bench/mod.rs index 45cbe243..9165f930 100644 --- a/crates/oxide_cli/src/bench/mod.rs +++ b/crates/oxide_cli/src/bench/mod.rs @@ -19,10 +19,8 @@ pub mod profile; pub mod rust_bench; use std::process::ExitCode; -use std::sync::Arc; -use oxide_kernel::kernel::KernelCore; -use oxide_vm::vm_pool::VmPool; +use oxide_engine::Engine; /// 一次 bench 运行的全部配置(来自 CLI `bench` 子命令参数)。 pub struct BenchConfig { @@ -39,22 +37,27 @@ pub struct BenchConfig { } /// 按 mode 分派到具体 benchmark 实现(js/rust/leak)。 -pub fn run_benchmarks(config: BenchConfig, kernel: Arc, pool: Arc) -> ExitCode { +/// +/// 引擎由调用方构造(内核与池的生命周期随引擎);子模块按 `kernel` / `pool` +/// 参数口径透传,内部逻辑不变。 +pub fn run_benchmarks(config: BenchConfig, engine: &Engine) -> ExitCode { + let kernel = engine.kernel(); + let pool = engine.pool(); match config.mode.as_str() { - "js" => js_stress::run_js_stress_bench(&config, &kernel, &pool), + "js" => js_stress::run_js_stress_bench(&config, kernel, pool), "rust" => rust_bench::run_rust_bench(config.filter.as_deref()), // leak 模式按 case 过滤参数分派校准用例;无匹配 case 时跑默认的固定脚本内存回归(run_leak_detect)。 "leak" => match config.filter.as_deref() { - Some("vm_creation") => leak_detect::run_mem_vm_creation_leak(&kernel), - Some("builtin_world_build") => leak_detect::run_mem_builtin_world_build(&kernel), - Some("dirty_rebuild") => leak_detect::run_mem_dirty_rebuild_leak(&kernel), - Some("closure_dead_leak") => leak_detect::run_mem_closure_dead_leak(&kernel), - Some("pool_high_water") => leak_detect::run_mem_pool_high_water(&kernel), - Some("object_churn_peak") => leak_detect::run_mem_object_churn_peak(&kernel), + Some("vm_creation") => leak_detect::run_mem_vm_creation_leak(kernel), + Some("builtin_world_build") => leak_detect::run_mem_builtin_world_build(kernel), + Some("dirty_rebuild") => leak_detect::run_mem_dirty_rebuild_leak(kernel), + Some("closure_dead_leak") => leak_detect::run_mem_closure_dead_leak(kernel), + Some("pool_high_water") => leak_detect::run_mem_pool_high_water(kernel), + Some("object_churn_peak") => leak_detect::run_mem_object_churn_peak(kernel), Some("kernel_lifetime") => leak_detect::run_mem_kernel_lifetime(), Some("startup_breakdown") => leak_detect::run_mem_startup_breakdown(), - Some("alloc_ab") => alloc_ab::run_alloc_ab(&config, &kernel, &pool), - _ => leak_detect::run_leak_detect(&config, &kernel, &pool), + Some("alloc_ab") => alloc_ab::run_alloc_ab(&config, kernel, pool), + _ => leak_detect::run_leak_detect(&config, kernel, pool), }, _ => { eprintln!("Unknown bench mode: {}. Use: js, rust, or leak", config.mode); diff --git a/crates/oxide_cli/src/main.rs b/crates/oxide_cli/src/main.rs index c9faa4cd..9ea160b5 100644 --- a/crates/oxide_cli/src/main.rs +++ b/crates/oxide_cli/src/main.rs @@ -3,7 +3,6 @@ use std::fs; use std::process::ExitCode; -use std::sync::Arc; use std::time::{Duration, Instant}; use ansi_term::Colour::Red; @@ -17,18 +16,16 @@ use oxide_cli::server::server::{run_server, run_server_rm, RmServerConfig, Serve use oxide_cli::server::sidecar; use oxide_cli::server::spawn; use oxide_cli::server::watchdog; -use oxide_compiler::compiler::{compiled_module_hash, Compiler}; +use oxide_compiler::compiler::Compiler; use oxide_compiler::compiler_error; -use oxide_compiler::DefaultCompilerService; -use oxide_kernel::kernel::{KernelConfig, KernelCore}; +use oxide_engine::Engine; +use oxide_kernel::kernel::KernelConfig; use oxide_kernel::shape_forge::ShapeForge; use oxide_kernel::string_forge::PermInterner; use oxide_kernel::{kernel_error, kernel_info}; use oxide_log::{Level, SUBSYSTEM_COUNT}; -use oxide_parser::Allocator; use oxide_vm::vm::Vm; use oxide_vm::vm_error; -use oxide_vm::vm_pool::VmPool; use oxide_vm::JsValue; mod bench; @@ -231,15 +228,13 @@ fn main() -> ExitCode { match cli.command { Some(Commands::Eval { code, trace }) => { - let kernel = make_kernel(cli.verbose, cli.quiet); - let pool = make_pool(&kernel); - eval(&code, &kernel, &pool, trace, cli.profile, true) + let engine = make_engine(cli.verbose, cli.quiet); + eval(&code, &engine, trace, cli.profile, true) } Some(Commands::Run { file, repeat, trace }) => { - let kernel = make_kernel(cli.verbose, cli.quiet); - let pool = make_pool(&kernel); + let engine = make_engine(cli.verbose, cli.quiet); for n in 0..repeat { - let code = run(&file, &kernel, &pool, trace, cli.profile); + let code = run(&file, &engine, trace, cli.profile); // 失败即终止并传播退出码,供脚本与 CI 区分成败。 if code != ExitCode::SUCCESS { return code; @@ -255,7 +250,10 @@ fn main() -> ExitCode { file, no_dce, no_regalloc, - }) => compile(expr, file, no_dce, no_regalloc), + }) => { + let engine = make_engine(false, false); + compile(&engine, expr, file, no_dce, no_regalloc) + } Some(Commands::Bench { mode, filter, @@ -265,8 +263,7 @@ fn main() -> ExitCode { sample, sample_top, }) => { - let kernel = make_kernel(false, false); - let pool = make_pool(&kernel); + let engine = make_engine(false, false); // 采样周期边界检查:须为 0(关闭)或 2 的幂,否则指令边界的 // `steps & (period - 1) == 0` 判定退化(周期不整除步数序列)。 if sample != 0 && sample & (sample - 1) != 0 { @@ -282,7 +279,7 @@ fn main() -> ExitCode { sample_period: sample, sample_top, }; - bench::run_benchmarks(config, kernel, pool) + bench::run_benchmarks(config, &engine) } Some(Commands::Test { .. }) => not_implemented("test"), Some(Commands::Server { command }) => server_command(command), @@ -608,7 +605,8 @@ fn start_result(result: Result<(), oxide_cli::server::server::ServerError>) -> E } } -fn make_kernel(verbose: bool, quiet: bool) -> Arc { +/// 建引擎:`KernelConfig::standard()` 按 -v / -q 填 `log_levels` 后 `Engine::new`。 +fn make_engine(verbose: bool, quiet: bool) -> Engine { let mut config = KernelConfig::standard(); if verbose { config.log_levels = [Level::Info; SUBSYSTEM_COUNT]; @@ -617,36 +615,11 @@ fn make_kernel(verbose: bool, quiet: bool) -> Arc { } else { config.log_levels = [Level::Error; SUBSYSTEM_COUNT]; } - KernelCore::new(config) + Engine::new(config) } -fn make_pool(kernel: &Arc) -> Arc { - VmPool::new( - Arc::clone(kernel), - Arc::new(DefaultCompilerService), - kernel.config.min_pool_size, - kernel.config.max_pool_size, - ) -} - -fn eval( - code: &str, kernel: &Arc, pool: &Arc, trace: bool, profile: bool, print_result: bool, -) -> ExitCode { - let allocator = Allocator::default(); - let program = match oxide_parser::parse(&allocator, code) { - Ok(p) => p, - Err(errors) => { - for err in &errors { - compiler_error!("parse error: {}", err); - eprintln!("{}", Red.paint(err.to_string())); - } - return ExitCode::FAILURE; - } - }; - - let compiler = Compiler::new(); - let hash = compiled_module_hash(&program); - let module = match kernel.code_forge().get_or_insert_with(hash, || compiler.compile(&program)) { +fn eval(code: &str, engine: &Engine, trace: bool, profile: bool, print_result: bool) -> ExitCode { + let module = match engine.compile(code) { Ok(m) => m, Err(err) => { compiler_error!("compile error: {}", err); @@ -655,7 +628,7 @@ fn eval( } }; - let mut guard = pool.spawn(); + let mut guard = engine.spawn(); guard.vm_mut().set_instruction_trace(trace); let exec_start = Instant::now(); match guard.vm_mut().run(&module) { @@ -669,6 +642,7 @@ fn eval( } // eval 臂按 REPL 语义打印完成值;run 臂脚本只输出自身产生内容。 if print_result { + let kernel = engine.kernel(); format_result(guard.vm(), kernel.perm_interner().as_ref(), kernel.shape_forge().as_ref(), result); } ExitCode::SUCCESS @@ -685,9 +659,9 @@ fn format_result(vm: &oxide_vm::vm::Vm, string_forge: &PermInterner, shape_forge println!("{}", format_js_value(vm, string_forge, shape_forge, val)); } -fn run(file: &str, kernel: &Arc, pool: &Arc, trace: bool, profile: bool) -> ExitCode { +fn run(file: &str, engine: &Engine, trace: bool, profile: bool) -> ExitCode { match fs::read_to_string(file) { - Ok(source) => eval(&source, kernel, pool, trace, profile, false), + Ok(source) => eval(&source, engine, trace, profile, false), Err(err) => { kernel_error!("cannot read {}: {}", file, err); eprintln!("{}", Red.paint(format!("Cannot read {file}: {err}"))); @@ -696,7 +670,7 @@ fn run(file: &str, kernel: &Arc, pool: &Arc, trace: bool, pr } } -fn compile(expr: Option, file: Option, no_dce: bool, no_regalloc: bool) -> ExitCode { +fn compile(engine: &Engine, expr: Option, file: Option, no_dce: bool, no_regalloc: bool) -> ExitCode { let source = if let Some(code) = expr { code } else if let Some(path) = file { @@ -714,26 +688,9 @@ fn compile(expr: Option, file: Option, no_dce: bool, no_regalloc return ExitCode::FAILURE; }; - let allocator = Allocator::default(); - let program = match oxide_parser::parse(&allocator, &source) { - Ok(p) => p, - Err(errors) => { - for err in &errors { - compiler_error!("parse error: {}", err); - eprintln!("{}", Red.paint(err.to_string())); - } - return ExitCode::FAILURE; - } - }; - - let mut compiler = Compiler::new(); - if no_dce { - compiler = compiler.with_dce(false); - } - if no_regalloc { - compiler = compiler.with_regalloc(false); - } - match compiler.compile(&program) { + // 旗标面经自定义编译器透传(无缺省臂,漏传即旗标失效)。 + let compiler = Compiler::new().with_dce(!no_dce).with_regalloc(!no_regalloc); + match engine.compile_with(&source, &compiler) { Ok(module) => { print!("{module}"); ExitCode::SUCCESS @@ -771,10 +728,9 @@ fn repl() -> ExitCode { } }; - let kernel = make_kernel(false, false); - let mut vm = Vm::with_kernel_core(Arc::clone(&kernel)); - // REPL 源经 eval 路径动态编译(含 `new Function` / eval 内置),注入真实编译服务。 - vm.set_compiler_service(Arc::new(DefaultCompilerService)); + let engine = make_engine(false, false); + // 常驻 Vm 不经池;动态编译服务由 `new_vm` 同口径注入。 + let mut vm = engine.new_vm(); let mut input_buf = String::new(); loop { @@ -801,7 +757,7 @@ fn repl() -> ExitCode { continue; } - let result = eval_repl(&input_buf, &kernel, &mut vm); + let result = eval_repl(&input_buf, &engine, &mut vm); input_buf.clear(); if result == ExitCode::FAILURE { @@ -825,22 +781,10 @@ fn repl() -> ExitCode { } } -fn eval_repl(code: &str, kernel: &Arc, vm: &mut Vm) -> ExitCode { - let allocator = Allocator::default(); - let program = match oxide_parser::parse(&allocator, code) { - Ok(p) => p, - Err(errors) => { - for err in &errors { - compiler_error!("parse error: {}", err); - eprintln!("{}", Red.paint(err.to_string())); - } - return ExitCode::FAILURE; - } - }; - +fn eval_repl(code: &str, engine: &Engine, vm: &mut Vm) -> ExitCode { + // REPL 持久模式:顶层 let/const 也写全局对象属性,经缓存编译。 let compiler = Compiler::new().with_repl_persist(true); - let hash = compiled_module_hash(&program); - let module = match kernel.code_forge().get_or_insert_with(hash, || compiler.compile(&program)) { + let module = match engine.compile_with(code, &compiler) { Ok(m) => m, Err(err) => { compiler_error!("compile error: {}", err); @@ -851,6 +795,7 @@ fn eval_repl(code: &str, kernel: &Arc, vm: &mut Vm) -> ExitCode { match vm.run(&module) { Ok(result) => { + let kernel = engine.kernel(); format_result(vm, kernel.perm_interner().as_ref(), kernel.shape_forge().as_ref(), result); ExitCode::SUCCESS } diff --git a/crates/oxide_engine/Cargo.toml b/crates/oxide_engine/Cargo.toml new file mode 100644 index 00000000..0b01d9fd --- /dev/null +++ b/crates/oxide_engine/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "oxide_engine" +version.workspace = true +edition.workspace = true +license.workspace = true +rust-version.workspace = true +description = "OxideJS - compile+run convenience layer (Engine)" + +[dependencies] +oxide_bytecode = { path = "../oxide_bytecode" } +oxide_parser = { path = "../oxide_parser" } +oxide_compiler = { path = "../oxide_compiler" } +oxide_kernel = { path = "../oxide_kernel" } +oxide_vm = { path = "../oxide_vm" } +oxide_types = { path = "../oxide_types" } diff --git a/crates/oxide_engine/src/lib.rs b/crates/oxide_engine/src/lib.rs new file mode 100644 index 00000000..bbc3bbf2 --- /dev/null +++ b/crates/oxide_engine/src/lib.rs @@ -0,0 +1,168 @@ +//! oxide_engine:compile+run 便捷层(执行层与入口层之间)。 +//! +//! `Engine` 持 `Arc` + `Arc`,把入口 crate 各自内联装配的 +//! 「parse → compile(经 CodeForge 缓存)→ run(经 VmPool 或独立 Vm)」流水线 +//! 收口为单一实现源。 +//! +//! 关键约定: +//! - 池恒建(`min/max_pool_size` 取自内核配置);不借池的入口(每测试独立 +//! Vm)付一次可忽略的预热成本,不引入可选池分支。 +//! - `compile` / `compile_with` 统一经 CodeForge 缓存;命中返回同一模块 +//! (结构哈希键,语义等价)。 +//! - `new_vm` 是不经池的独立 Vm;动态编译服务与池内 Vm 同口径注入 +//! (缺省编译服务),调用方无需再注入。 + +use std::sync::Arc; + +use oxide_bytecode::module::CompiledModule; +use oxide_compiler::compiler::{compiled_module_hash, Compiler}; +use oxide_compiler::DefaultCompilerService; +use oxide_kernel::kernel::{KernelConfig, KernelCore}; +use oxide_vm::vm::Vm; +use oxide_vm::vm_pool::{VmGuard, VmPool}; +use oxide_vm::JsValue; + +/// 引擎便捷类型:持共享内核与 VM 池,提供 compile / eval / spawn / new_vm 面。 +/// +/// 入口 crate(CLI、test262 runner)按进程或按 worker 各建一个,经其方法完成 +/// parse → compile → run 流水线;同一引擎内全部 Vm 共享同一内核与池。 +pub struct Engine { + kernel: Arc, + pool: Arc, +} + +impl Engine { + /// 由内核配置建引擎:`KernelCore::new` 后按配置 `min/max_pool_size` 建池。 + /// + /// # 边界与前提 + /// - `min_pool_size` 受 `max_pool_size` 钳制(池预热路径同口径)。 + /// + /// # 副作用 + /// - 初始化日志系统(`KernelCore::new` 内完成,幂等)。 + /// - 同步预热 `min_pool_size` 个 Vm。 + pub fn new(config: KernelConfig) -> Self { + let kernel = KernelCore::new(config); + let pool = VmPool::new( + Arc::clone(&kernel), + Arc::new(DefaultCompilerService), + kernel.config.min_pool_size, + kernel.config.max_pool_size, + ); + Self { kernel, pool } + } + + /// parse + 缺省编译(`Compiler::new()`),经 CodeForge 缓存,返回模块。 + /// + /// # 边界与前提 + /// - 解析失败返回各条消息换行拼接的单一错误串。 + /// + /// # 副作用 + /// - 缓存未命中时编译产物写入 CodeForge。 + pub fn compile(&self, source: &str) -> Result, String> { + self.compile_with(source, &Compiler::new()) + } + + /// parse + 自定义编译器编译,经 CodeForge 缓存,返回模块。 + /// + /// 供 `no_dce` / `no_regalloc` / `repl_persist` 等旗标面:编译器由调用方 + /// 构造传入,无缺省臂,漏传即旗标失效。 + /// + /// # 步骤 + /// 1. 局部分配器上解析源码为 Program。 + /// 2. 结构哈希作缓存键,CodeForge 未命中时调给定编译器编译并缓存。 + pub fn compile_with(&self, source: &str, compiler: &Compiler) -> Result, String> { + // 局部分配器:AST 节点经 oxc bump 分配器分配,编译完成即弃。 + let allocator = oxide_parser::Allocator::default(); + let program = oxide_parser::parse(&allocator, source) + .map_err(|errs| errs.into_iter().map(|e| e.message).collect::>().join("\n"))?; + + let hash = compiled_module_hash(&program); + self.kernel.code_forge().get_or_insert_with(hash, || compiler.compile(&program)) + } + + /// 编译 + 池借出 Vm 执行,返回完成值(eval 便捷路径)。 + pub fn eval(&self, source: &str) -> Result { + let module = self.compile(source)?; + let mut guard = self.pool.spawn(); + guard.vm_mut().run(&module) + } + + /// 池借出 Vm(RAII `VmGuard`,drop 归还池)。 + pub fn spawn(&self) -> VmGuard { + self.pool.spawn() + } + + /// 建独立 Vm(不经池),供常驻 / 每测试独立场景。 + /// + /// 动态编译服务与池内 Vm 同口径注入(缺省编译服务),调用方无需再注入。 + pub fn new_vm(&self) -> Vm { + let mut vm = Vm::with_kernel_core(Arc::clone(&self.kernel)); + vm.set_compiler_service(Arc::new(DefaultCompilerService)); + vm + } + + /// 只读访问共享内核。 + pub fn kernel(&self) -> &Arc { + &self.kernel + } + + /// 只读访问 VM 池。 + pub fn pool(&self) -> &Arc { + &self.pool + } +} + +#[cfg(test)] +mod tests { + use super::*; + use oxide_kernel::kernel::KernelConfig; + + fn test_engine() -> Engine { + Engine::new(KernelConfig::minimal()) + } + + /// compile 把产物写入 CodeForge 缓存:同源码二次编译命中缓存, + /// 返回同一模块且条目数不增。 + #[test] + fn compile_caches_module_in_code_forge() { + let engine = test_engine(); + let first = engine.compile("1 + 2").expect("编译应成功"); + assert_eq!(engine.kernel().code_forge().len(), 1, "首次编译应产生一个缓存条目"); + + let second = engine.compile("1 + 2").expect("编译应成功"); + assert!(Arc::ptr_eq(&first, &second), "同源码二次编译应命中缓存返回同一模块"); + assert_eq!(engine.kernel().code_forge().len(), 1, "缓存命中不应新增条目"); + } + + /// eval 走编译 + 池借出 + 执行全路径,返回完成值。 + #[test] + fn eval_returns_completion_value() { + let engine = test_engine(); + let value = engine.eval("6 * 7.0").expect("执行应成功"); + assert!(value.is_number(), "完成值应为数字"); + assert_eq!(value.as_double(), 42.0, "完成值应为 42"); + } + + /// new_vm 是不经池的独立 Vm:能独立执行引擎的编译产物。 + #[test] + fn new_vm_runs_independently() { + let engine = test_engine(); + let module = engine.compile("10 + 5").expect("编译应成功"); + let mut vm = engine.new_vm(); + let value = vm.run(&module).expect("执行应成功"); + assert!(value.is_number(), "完成值应为数字"); + assert_eq!(value.as_int(), 15, "完成值应为 15"); + } + + /// 解析失败映射为单一错误串(各条消息换行拼接),且不污染缓存。 + #[test] + fn parse_error_maps_to_joined_message() { + let engine = test_engine(); + let err = match engine.compile("const x = {") { + Ok(_) => panic!("解析失败应返回错误"), + Err(e) => e, + }; + assert!(!err.is_empty(), "解析错误消息不应为空"); + assert_eq!(engine.kernel().code_forge().len(), 0, "解析失败不应写入缓存"); + } +} diff --git a/crates/oxide_test262/Cargo.toml b/crates/oxide_test262/Cargo.toml index ab2f46b7..300b2a50 100644 --- a/crates/oxide_test262/Cargo.toml +++ b/crates/oxide_test262/Cargo.toml @@ -17,6 +17,7 @@ oxide_emit = { path = "../oxide_emit" } oxide_vm = { path = "../oxide_vm" } oxide_parser = { path = "../oxide_parser" } oxide_kernel = { path = "../oxide_kernel" } +oxide_engine = { path = "../oxide_engine" } oxide_types = { path = "../oxide_types" } oxide_log = { path = "../oxide_log" } serde = { version = "1", features = ["derive"] } diff --git a/crates/oxide_test262/src/main.rs b/crates/oxide_test262/src/main.rs index a4759798..b2416611 100644 --- a/crates/oxide_test262/src/main.rs +++ b/crates/oxide_test262/src/main.rs @@ -25,7 +25,7 @@ use heartbeat::write_heartbeat; use judge::{categorize_fail, TestOutcome}; use oxide_log::{Level, LogConfig, Output, SUBSYSTEM_COUNT}; use report::{append_fail_log, first_line, format_fail_categories, format_fail_groupings, format_fail_list}; -use runner::{build_runner_kernel, process_path, CURRENT_TEST_PATH}; +use runner::{build_runner_engine, process_path, CURRENT_TEST_PATH}; use stats::RunStats; use supervise::run_supervised; @@ -145,7 +145,7 @@ fn run_tests() -> bool { // 确定 worker 数。`KernelCore` + session 状态是 `!Send`(它持有 // 经 Arc 共享的一个 kernel;相反每个 worker 构建并拥有自己的 - // kernel + harness 源注册表 + 前缀缓存。只有 `PathBuf` 和 + // 引擎 + harness 源注册表 + 前缀缓存。只有 `PathBuf` 和 // `TestResult`(均 `Send`)跨线程。worker 从共享原子游标取测试下标, // 实现动态负载均衡。 let default_workers = if config.no_skip { @@ -228,7 +228,7 @@ fn run_tests() -> bool { std::thread::Builder::new() .stack_size(16 * 1024 * 1024) .spawn_scoped(scope, move || { - let mut kernel = build_runner_kernel(); + let mut engine = build_runner_engine(); let harness_sources = HARNESS.get_or_init(HarnessSources::new); let mut stats = RunStats::default(); let mut tests_since_kernel_reset = 0usize; @@ -254,7 +254,7 @@ fn run_tests() -> bool { filter, no_skip, no_regalloc, - &kernel, + &engine, harness_sources, &harness_cache, ); @@ -302,11 +302,11 @@ fn run_tests() -> bool { // 重建即全新 forge(结构性必清):旧核连同其表整体丢弃, // 先 sweep 是对 doomed 核白做一次 O(50k) 清理。 // 重建边界契约:旧核上无存活 VM(VM 每测试局部,此处作用域外)。 - debug_assert!(kernel.active_vms() == 0, "kernel rebuild requires no live VMs"); - kernel = build_runner_kernel(); + debug_assert!(engine.kernel().active_vms() == 0, "kernel rebuild requires no live VMs"); + engine = build_runner_engine(); tests_since_kernel_reset = 0; } else if done % 500 == 0 { - kernel.sweep_runner_forges(); // 批内 50k 兜底(数据依赖) + engine.kernel().sweep_runner_forges(); // 批内 50k 兜底(数据依赖) } if done % 500 == 0 || done == total { test262_info!("progress: {}/{} ({}%)", done, total, done * 100 / total); diff --git a/crates/oxide_test262/src/runner.rs b/crates/oxide_test262/src/runner.rs index ac9182d9..198b835d 100644 --- a/crates/oxide_test262/src/runner.rs +++ b/crates/oxide_test262/src/runner.rs @@ -11,8 +11,8 @@ use crate::judge::{ }; use crate::meta::{parse_meta, strip_meta, TestMeta}; use oxide_compiler::compiler::Compiler; -use oxide_compiler::DefaultCompilerService; -use oxide_kernel::kernel::{KernelConfig, KernelCore}; +use oxide_engine::Engine; +use oxide_kernel::kernel::KernelConfig; use oxide_types::value::JsValue; use oxide_vm::vm::Vm; use std::path::{Path, PathBuf}; @@ -38,13 +38,13 @@ fn pc_watch_path() -> Option<&'static PathBuf> { /// 在 catch_unwind 保护下运行单个测试,把引擎 panic 记为失败。 #[expect(clippy::too_many_arguments)] fn run_test( - path: &Path, source: &str, meta: &TestMeta, kernel: &Arc, harness: &HarnessSources, + path: &Path, source: &str, meta: &TestMeta, engine: &Engine, harness: &HarnessSources, 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, no_regalloc) + run_test_inner(path, source, meta, engine, harness, harness_cache, no_skip, no_regalloc) })); match result { @@ -96,9 +96,12 @@ impl oxide_emit::module::ModuleSourceLoader for Test262ModuleLoader { /// 单测执行主流程:拼 harness 前缀 → parse → compile → run; /// 依据 `negative` 元数据校验期望错误,未实现特性按 no_skip 选择跳过或失败。 +/// +/// script 臂经 `Engine::compile` 便捷路径(parse + CodeForge 缓存);module 臂 +/// 保留本模块的 `parse_module` + 依赖加载器(引擎暂不吸收 module 加载器面)。 #[expect(clippy::too_many_arguments)] fn run_test_inner( - path: &Path, source: &str, meta: &TestMeta, kernel: &Arc, harness: &HarnessSources, + path: &Path, source: &str, meta: &TestMeta, engine: &Engine, harness: &HarnessSources, harness_cache: &Arc>, no_skip: bool, no_regalloc: bool, ) -> TestResult { let start = std::time::Instant::now(); @@ -135,30 +138,31 @@ fn run_test_inner( } }; - let alloc = oxide_parser::Allocator::default(); - let program = match if is_module { - oxide_parser::parse_module(&alloc, &code) - } else { - oxide_parser::parse(&alloc, &code) - } { - Ok(p) => p, - Err(errs) => { - let dur = start.elapsed().as_millis() as u64; - let msg = format!("parse error: {}", errs[0].message); - if meta.negative.is_some() { - return TestResult::pass(path.to_path_buf(), dur, msg); + // module 臂保留本模块的 parse_module + 依赖加载器;script 臂经引擎 + // 便捷路径(parse + CodeForge 缓存)。 + let module_result = if is_module { + let alloc = oxide_parser::Allocator::default(); + let program = match oxide_parser::parse_module(&alloc, &code) { + Ok(p) => p, + Err(errs) => { + let dur = start.elapsed().as_millis() as u64; + let msg = format!("parse error: {}", errs[0].message); + if meta.negative.is_some() { + return TestResult::pass(path.to_path_buf(), dur, msg); + } + return TestResult::fail(path.to_path_buf(), dur, msg); } - return TestResult::fail(path.to_path_buf(), dur, msg); - } + }; + let compiler = if no_regalloc { Compiler::new().with_regalloc(false) } else { Compiler::new() }; + let mut loader = Test262ModuleLoader; + compiler + .compile_module(&program, path.to_string_lossy().as_ref(), &mut loader) + .map(Arc::new) + } else { + engine.compile(&code) }; - let compiler = if no_regalloc { Compiler::new().with_regalloc(false) } else { Compiler::new() }; - let module = match if is_module { - let mut loader = Test262ModuleLoader; - compiler.compile_module(&program, path.to_string_lossy().as_ref(), &mut loader) - } else { - compiler.compile(&program) - } { + let module = match module_result { Ok(m) => m, Err(e) => { let dur = start.elapsed().as_millis() as u64; @@ -171,14 +175,14 @@ fn run_test_inner( } }; - let mut vm = Vm::with_kernel_core(Arc::clone(kernel)); - // test262 语料含 eval / Function 动态编译面,注入真实编译服务。 - vm.set_compiler_service(Arc::new(DefaultCompilerService)); + // 独立 Vm 不经池;动态编译服务由 `new_vm` 同口径注入(语料含 eval / + // Function 动态编译面)。 + let mut vm = engine.new_vm(); // 监督者注入现场文件路径时开启 VM 的 last-pc 定频写(未注入时零开销)。 if let Some(watch) = pc_watch_path() { vm.set_pc_watch(Some(watch.clone())); } - let run_result = vm.run(&Arc::new(module)); + let run_result = vm.run(&module); let dur = start.elapsed().as_millis() as u64; if is_async { @@ -216,9 +220,9 @@ pub(crate) fn read_async_output(vm: &Vm) -> String { /// 串行与并行执行路径共享的每测试管线: /// 读文件、解析元数据、应用跳过过滤,然后运行。恰好返回一个 `TestResult`。 -/// worker 自有状态(`kernel`、`harness_sources`、`harness_cache`)永不跨线程。 +/// worker 自有状态(`engine`、`harness_sources`、`harness_cache`)永不跨线程。 pub(crate) fn process_path( - path: &Path, filter: &Option, no_skip: bool, no_regalloc: bool, kernel: &Arc, + path: &Path, filter: &Option, no_skip: bool, no_regalloc: bool, engine: &Engine, harness_sources: &HarnessSources, harness_cache: &Arc>, ) -> TestResult { let source = match std::fs::read_to_string(path) { @@ -258,14 +262,14 @@ pub(crate) fn process_path( } } - run_test(path, &source, &meta, kernel, harness_sources, harness_cache, no_skip, no_regalloc) + run_test(path, &source, &meta, engine, harness_sources, harness_cache, no_skip, no_regalloc) } -/// 构建带步数上限的 runner kernel。每个并行 worker 拥有自己的 kernel, +/// 构建带步数上限的 runner 引擎。每个并行 worker 拥有自己的引擎, /// 因为 `KernelCore` + session 状态是 `!Send`(持有 `P` = /// `Arc`,而 `JsObject` 存有裸 `*mut u8` 属性指针)。任何 kernel /// 形态的对象都不能跨线程边界,因此共享不可能;每 worker 自建是唯一正确设计。 -pub(crate) fn build_runner_kernel() -> Arc { +pub(crate) fn build_runner_engine() -> Engine { // 约束每个测试的执行步数,使单个死循环 / 未支持特性循环失败(或跳过) // 而非拖垮整个运行。VM 超限时抛 "VM step limit exceeded" 错误, // runner 将其归类为 step-limit 结果(默认 skip,--no-skip 下 fail)。 @@ -283,7 +287,7 @@ pub(crate) fn build_runner_kernel() -> Arc { // character-class 生成大表),512MiB 上限留 ~252MiB 余量; // 主要作用是把失控测试的驻留面封顶到上限本身。 kernel_config.max_alloc_bytes = Some(512 * 1024 * 1024); - KernelCore::new(kernel_config) + Engine::new(kernel_config) } #[cfg(test)] @@ -293,6 +297,6 @@ mod tests { /// 分配上限常量与错误串单测的字面值机器耦合:改 cap 本钉必红。 #[test] fn runner_alloc_cap_matches_error_string_tests() { - assert_eq!(build_runner_kernel().config.max_alloc_bytes, Some(512 * 1024 * 1024)); + assert_eq!(build_runner_engine().kernel().config.max_alloc_bytes, Some(512 * 1024 * 1024)); } }