refactor: 新建 oxide_engine crate 统一两入口 compile-run 装配

This commit is contained in:
2026-10-08 06:26:01 +00:00
parent 4f9b6ab444
commit 10f97100cd
10 changed files with 317 additions and 165 deletions
Generated
+32 -18
View File
@@ -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",
+2 -1
View File
@@ -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"
+1
View File
@@ -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
+16 -13
View File
@@ -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<KernelCore>, pool: Arc<VmPool>) -> 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);
+34 -89
View File
@@ -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<KernelCore> {
/// 建引擎:`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<KernelCore> {
} else {
config.log_levels = [Level::Error; SUBSYSTEM_COUNT];
}
KernelCore::new(config)
Engine::new(config)
}
fn make_pool(kernel: &Arc<KernelCore>) -> Arc<VmPool> {
VmPool::new(
Arc::clone(kernel),
Arc::new(DefaultCompilerService),
kernel.config.min_pool_size,
kernel.config.max_pool_size,
)
}
fn eval(
code: &str, kernel: &Arc<KernelCore>, pool: &Arc<VmPool>, 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<KernelCore>, pool: &Arc<VmPool>, 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<KernelCore>, pool: &Arc<VmPool>, trace: bool, pr
}
}
fn compile(expr: Option<String>, file: Option<String>, no_dce: bool, no_regalloc: bool) -> ExitCode {
fn compile(engine: &Engine, expr: Option<String>, file: Option<String>, 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<String>, file: Option<String>, 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<KernelCore>, 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<KernelCore>, 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
}
+15
View File
@@ -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" }
+168
View File
@@ -0,0 +1,168 @@
//! oxide_engine:compile+run 便捷层(执行层与入口层之间)。
//!
//! `Engine` 持 `Arc<KernelCore>` + `Arc<VmPool>`,把入口 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<KernelCore>,
pool: Arc<VmPool>,
}
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<Arc<CompiledModule>, 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<Arc<CompiledModule>, 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::<Vec<_>>().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<JsValue, String> {
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<KernelCore> {
&self.kernel
}
/// 只读访问 VM 池。
pub fn pool(&self) -> &Arc<VmPool> {
&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, "解析失败不应写入缓存");
}
}
+1
View File
@@ -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"] }
+7 -7
View File
@@ -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);
+30 -26
View File
@@ -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<KernelCore>, harness: &HarnessSources,
path: &Path, source: &str, meta: &TestMeta, engine: &Engine, harness: &HarnessSources,
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, 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<KernelCore>, harness: &HarnessSources,
path: &Path, source: &str, meta: &TestMeta, engine: &Engine, harness: &HarnessSources,
harness_cache: &Arc<RwLock<HarnessPrefixCache>>, no_skip: bool, no_regalloc: bool,
) -> TestResult {
let start = std::time::Instant::now();
@@ -135,12 +138,11 @@ fn run_test_inner(
}
};
// module 臂保留本模块的 parse_module + 依赖加载器;script 臂经引擎
// 便捷路径(parse + CodeForge 缓存)。
let module_result = if is_module {
let alloc = oxide_parser::Allocator::default();
let program = match if is_module {
oxide_parser::parse_module(&alloc, &code)
} else {
oxide_parser::parse(&alloc, &code)
} {
let program = match oxide_parser::parse_module(&alloc, &code) {
Ok(p) => p,
Err(errs) => {
let dur = start.elapsed().as_millis() as u64;
@@ -151,14 +153,16 @@ fn run_test_inner(
return TestResult::fail(path.to_path_buf(), dur, msg);
}
};
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)
compiler
.compile_module(&program, path.to_string_lossy().as_ref(), &mut loader)
.map(Arc::new)
} else {
compiler.compile(&program)
} {
engine.compile(&code)
};
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<String>, no_skip: bool, no_regalloc: bool, kernel: &Arc<KernelCore>,
path: &Path, filter: &Option<String>, no_skip: bool, no_regalloc: bool, engine: &Engine,
harness_sources: &HarnessSources, harness_cache: &Arc<RwLock<HarnessPrefixCache>>,
) -> 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<JsObject>` =
/// `Arc<JsObject>`,而 `JsObject` 存有裸 `*mut u8` 属性指针)。任何 kernel
/// 形态的对象都不能跨线程边界,因此共享不可能;每 worker 自建是唯一正确设计。
pub(crate) fn build_runner_kernel() -> Arc<KernelCore> {
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<KernelCore> {
// 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));
}
}