From 9084aff761edd31c62bf0dd893bb781e5f2a1bae Mon Sep 17 00:00:00 2001 From: pjh456 <147148383@qq.com> Date: Mon, 12 Oct 2026 03:35:12 +0000 Subject: [PATCH] =?UTF-8?q?perf:=20=E8=A7=A3=E6=9E=90=E5=85=A5=E5=8F=A3?= =?UTF-8?q?=E6=94=B9=E7=94=A8=E7=BA=BF=E7=A8=8B=E6=9C=AC=E5=9C=B0=E5=88=86?= =?UTF-8?q?=E9=85=8D=E5=99=A8=EF=BC=8C=E7=A8=B3=E6=80=81=E5=85=8D=E7=B3=BB?= =?UTF-8?q?=E7=BB=9F=E8=B0=83=E7=94=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Cargo.lock | 40 +++++++------- Cargo.toml | 2 +- crates/oxide_cli/src/server/eval.rs | 31 +++++------ crates/oxide_compiler/src/compiler_service.rs | 30 +++++----- crates/oxide_emit/src/module.rs | 45 ++++++++------- crates/oxide_engine/src/lib.rs | 14 ++--- crates/oxide_parser/src/lib.rs | 55 +++++++++++++++++++ crates/oxide_test262/src/runner.rs | 41 ++++++++------ 8 files changed, 161 insertions(+), 97 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 1a4a3b84..aaf900be 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1160,7 +1160,7 @@ dependencies = [ [[package]] name = "oxide_builtins" -version = "0.16.1" +version = "0.16.2" dependencies = [ "chrono", "fast-float", @@ -1185,7 +1185,7 @@ dependencies = [ [[package]] name = "oxide_bytecode" -version = "0.16.1" +version = "0.16.2" dependencies = [ "num-bigint", "oxide_log", @@ -1193,7 +1193,7 @@ dependencies = [ [[package]] name = "oxide_cfg" -version = "0.16.1" +version = "0.16.2" dependencies = [ "oxide_bytecode", "oxide_emit", @@ -1204,7 +1204,7 @@ dependencies = [ [[package]] name = "oxide_cli" -version = "0.16.1" +version = "0.16.2" dependencies = [ "ansi_term", "bumpalo", @@ -1228,7 +1228,7 @@ dependencies = [ [[package]] name = "oxide_code_cache" -version = "0.16.1" +version = "0.16.2" dependencies = [ "lru", "oxide_bytecode", @@ -1237,7 +1237,7 @@ dependencies = [ [[package]] name = "oxide_compiler" -version = "0.16.1" +version = "0.16.2" dependencies = [ "oxide_bytecode", "oxide_cfg", @@ -1257,7 +1257,7 @@ dependencies = [ [[package]] name = "oxide_dce" -version = "0.16.1" +version = "0.16.2" dependencies = [ "oxide_bytecode", "oxide_cfg", @@ -1271,7 +1271,7 @@ dependencies = [ [[package]] name = "oxide_emit" -version = "0.16.1" +version = "0.16.2" dependencies = [ "num-bigint", "oxc_allocator", @@ -1286,7 +1286,7 @@ dependencies = [ [[package]] name = "oxide_engine" -version = "0.16.1" +version = "0.16.2" dependencies = [ "oxide_bytecode", "oxide_compiler", @@ -1298,7 +1298,7 @@ dependencies = [ [[package]] name = "oxide_ir" -version = "0.16.1" +version = "0.16.2" dependencies = [ "oxide_bytecode", "oxide_log", @@ -1307,7 +1307,7 @@ dependencies = [ [[package]] name = "oxide_kernel" -version = "0.16.1" +version = "0.16.2" dependencies = [ "bumpalo", "dashmap", @@ -1321,7 +1321,7 @@ dependencies = [ [[package]] name = "oxide_liveness" -version = "0.16.1" +version = "0.16.2" dependencies = [ "oxide_bytecode", "oxide_cfg", @@ -1333,7 +1333,7 @@ dependencies = [ [[package]] name = "oxide_log" -version = "0.16.1" +version = "0.16.2" dependencies = [ "tracing", "tracing-appender", @@ -1342,11 +1342,11 @@ dependencies = [ [[package]] name = "oxide_net" -version = "0.16.1" +version = "0.16.2" [[package]] name = "oxide_parser" -version = "0.16.1" +version = "0.16.2" dependencies = [ "oxc_allocator", "oxc_ast", @@ -1361,7 +1361,7 @@ dependencies = [ [[package]] name = "oxide_regalloc" -version = "0.16.1" +version = "0.16.2" dependencies = [ "oxide_bytecode", "oxide_cfg", @@ -1377,7 +1377,7 @@ dependencies = [ [[package]] name = "oxide_runtime_api" -version = "0.16.1" +version = "0.16.2" dependencies = [ "num-bigint", "num-traits", @@ -1390,7 +1390,7 @@ dependencies = [ [[package]] name = "oxide_test262" -version = "0.16.1" +version = "0.16.2" dependencies = [ "oxide_bytecode", "oxide_compiler", @@ -1410,7 +1410,7 @@ dependencies = [ [[package]] name = "oxide_types" -version = "0.16.1" +version = "0.16.2" dependencies = [ "bumpalo", "dashmap", @@ -1423,7 +1423,7 @@ dependencies = [ [[package]] name = "oxide_vm" -version = "0.16.1" +version = "0.16.2" dependencies = [ "bumpalo", "chrono", diff --git a/Cargo.toml b/Cargo.toml index e25e64ec..aa40675f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,7 +24,7 @@ members = [ ] [workspace.package] -version = "0.16.1" +version = "0.16.2" edition = "2021" license = "MIT" rust-version = "1.80" diff --git a/crates/oxide_cli/src/server/eval.rs b/crates/oxide_cli/src/server/eval.rs index ea73bb36..298bb4c5 100644 --- a/crates/oxide_cli/src/server/eval.rs +++ b/crates/oxide_cli/src/server/eval.rs @@ -12,7 +12,6 @@ use std::sync::Arc; use oxide_compiler::compiler::{compiled_module_hash, Compiler}; use oxide_kernel::kernel::KernelCore; -use oxide_parser::Allocator; use oxide_vm::vm_pool::VmPool; use super::protocol::ServerResponse; @@ -20,7 +19,7 @@ use super::protocol::ServerResponse; /// 执行请求处理:解析 → 编译 → 取 VM → 执行 → 渲染完成值 → 归还池的六段路径。 /// /// # 步骤 -/// 1. parse:局部分配器加 `oxide_parser::parse`,失败时各条错误换行拼接成错误帧。 +/// 1. parse:线程本地分配器加 `oxide_parser::parse`,失败时各条错误换行拼接成错误帧。 /// 2. compile:`Compiler` 加 `CodeForge` 结构哈希缓存,失败以原文成错误帧。 /// 3. spawn:从池取 `VmGuard`(闭包内创建,panic 时随展开被 drop)。 /// 4. run:设每请求步数覆盖(含 None)后 `run`,`Err` 即错误帧。 @@ -38,22 +37,22 @@ use super::protocol::ServerResponse; /// - release 下 `panic=abort` 使 `catch_unwind` 空操作,watchdog 是崩溃后恢复的唯一自动手段。 pub fn handle_eval(code: &str, max_steps: Option, kernel: &Arc, pool: &Arc) -> ServerResponse { let response = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| { - // parse:局部分配器,解析失败各条换行拼接成错误帧。 - let allocator = Allocator::default(); - let program = match oxide_parser::parse(&allocator, code) { - Ok(program) => program, - Err(errors) => { + // parse + compile:线程本地分配器,AST 生命周期被闭包边界约束(不逃逸)。 + // 解析失败各条换行拼接成错误帧,编译失败以原文成错误帧。 + let module = match oxide_parser::with_parse_allocator(|allocator| { + let program = oxide_parser::parse(allocator, code).map_err(|errors| { let message = errors.iter().map(|e| e.to_string()).collect::>().join("\n"); - return ServerResponse::eval_err(message); - } - }; - - // compile:结构哈希缓存,失败以原文成错误帧。 - let compiler = Compiler::new(); - let hash = compiled_module_hash(&program); - let module = match kernel.code_forge().get_or_insert_with(hash, || compiler.compile(&program)) { + ServerResponse::eval_err(message) + })?; + let compiler = Compiler::new(); + let hash = compiled_module_hash(&program); + kernel + .code_forge() + .get_or_insert_with(hash, || compiler.compile(&program)) + .map_err(ServerResponse::eval_err) + }) { Ok(module) => module, - Err(message) => return ServerResponse::eval_err(message), + Err(response) => return response, }; // spawn:从池取 VM;guard 在闭包内创建,panic 时随展开被 drop。 diff --git a/crates/oxide_compiler/src/compiler_service.rs b/crates/oxide_compiler/src/compiler_service.rs index d801a1b3..1e61e10b 100644 --- a/crates/oxide_compiler/src/compiler_service.rs +++ b/crates/oxide_compiler/src/compiler_service.rs @@ -1,5 +1,5 @@ //! 默认编译服务:串联 `oxide_parser::parse` 与 `Compiler::compile`, -//! parse 错误映射与 `Allocator` 构造的唯一实现源。 +//! parse 错误映射的唯一实现源。 use oxide_bytecode::module::CompiledModule; use oxide_parser::Program; @@ -13,22 +13,24 @@ pub struct DefaultCompilerService; impl CompilerService for DefaultCompilerService { fn compile_script(&self, source: &str) -> Result { - // 局部分配器:AST 节点经 oxc bump 分配器分配,编译完成即弃。 - let allocator = oxide_parser::Allocator::default(); - let program = parse_program(&allocator, source)?; - // 动态路径源契约:调用方以源码域转义(`string_forge::source_escape`) - // 形态传源,故置 source_encoded。 - Compiler::new().with_source_encoded(true).compile(&program) + // 线程本地分配器:AST 节点经 oxc bump 分配器分配,编译完成即弃。 + oxide_parser::with_parse_allocator(|allocator| { + let program = parse_program(allocator, source)?; + // 动态路径源契约:调用方以源码域转义(`string_forge::source_escape`) + // 形态传源,故置 source_encoded。 + Compiler::new().with_source_encoded(true).compile(&program) + }) } fn compile_eval_script(&self, source: &str) -> Result { - let allocator = oxide_parser::Allocator::default(); - let program = parse_program(&allocator, source)?; - // eval 脚本:顶层 var/function 声明落全局属性 configurable:true。 - Compiler::new() - .with_eval_script(true) - .with_source_encoded(true) - .compile(&program) + oxide_parser::with_parse_allocator(|allocator| { + let program = parse_program(allocator, source)?; + // eval 脚本:顶层 var/function 声明落全局属性 configurable:true。 + Compiler::new() + .with_eval_script(true) + .with_source_encoded(true) + .compile(&program) + }) } } diff --git a/crates/oxide_emit/src/module.rs b/crates/oxide_emit/src/module.rs index ea06e6fc..6214cab8 100644 --- a/crates/oxide_emit/src/module.rs +++ b/crates/oxide_emit/src/module.rs @@ -958,27 +958,30 @@ impl Emitter { fn compile_js_dep( &self, resolved: &ResolvedModule, loader: &mut dyn ModuleSourceLoader, path_stack: &mut Vec, ) -> Result<(IRFunction, bool, HashSet, HashSet), String> { - let alloc = oxide_parser::Allocator::default(); - let program = oxide_parser::parse_module(&alloc, &resolved.source).map_err(|errs| { - format!( - "module parse error: {}", - errs.first().map(|e| e.message.as_str()).unwrap_or("parse failed") - ) - })?; - let mut ctx = CompileCtx::new(); - ctx.is_global_scope = false; - // 依赖模块经 parse_module 解析,顶层恒严格模式。 - ctx.is_strict = true; - self.emit_module_into_ctx(&program, &resolved.path, loader, path_stack, &mut ctx, false)?; - let reassignable = ctx.module_live_dep; - let own_export_names = module_own_export_names(&program.body); - let var_scoped = module_var_scoped_export_names(&program.body); - Ok(( - ctx.assemble_ir(ParamLayout { base: 0, count: 0 }, None), - reassignable, - own_export_names, - var_scoped, - )) + // 线程本地分配器:依赖模块递归解析共享同一分配器, + // 重入深度守卫防止嵌套 reset 销毁外层 AST。 + oxide_parser::with_parse_allocator(|alloc| { + let program = oxide_parser::parse_module(alloc, &resolved.source).map_err(|errs| { + format!( + "module parse error: {}", + errs.first().map(|e| e.message.as_str()).unwrap_or("parse failed") + ) + })?; + let mut ctx = CompileCtx::new(); + ctx.is_global_scope = false; + // 依赖模块经 parse_module 解析,顶层恒严格模式。 + ctx.is_strict = true; + self.emit_module_into_ctx(&program, &resolved.path, loader, path_stack, &mut ctx, false)?; + let reassignable = ctx.module_live_dep; + let own_export_names = module_own_export_names(&program.body); + let var_scoped = module_var_scoped_export_names(&program.body); + Ok(( + ctx.assemble_ir(ParamLayout { base: 0, count: 0 }, None), + reassignable, + own_export_names, + var_scoped, + )) + }) } /// 发 defer 预注册:对 deferred namespace 对象逐名 `__modulePreRegister`, diff --git a/crates/oxide_engine/src/lib.rs b/crates/oxide_engine/src/lib.rs index bbc3bbf2..18fc2be8 100644 --- a/crates/oxide_engine/src/lib.rs +++ b/crates/oxide_engine/src/lib.rs @@ -68,16 +68,16 @@ impl Engine { /// 构造传入,无缺省臂,漏传即旗标失效。 /// /// # 步骤 - /// 1. 局部分配器上解析源码为 Program。 + /// 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"))?; + oxide_parser::with_parse_allocator(|allocator| { + 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)) + let hash = compiled_module_hash(&program); + self.kernel.code_forge().get_or_insert_with(hash, || compiler.compile(&program)) + }) } /// 编译 + 池借出 Vm 执行,返回完成值(eval 便捷路径)。 diff --git a/crates/oxide_parser/src/lib.rs b/crates/oxide_parser/src/lib.rs index 3bf46fcd..7f92e726 100644 --- a/crates/oxide_parser/src/lib.rs +++ b/crates/oxide_parser/src/lib.rs @@ -4,6 +4,8 @@ mod error; mod parser_log; mod redeclaration_filter; +use std::cell::{Cell, UnsafeCell}; + /// 解析错误:消息 + 源文件字节区间(起始,结束)。 pub use error::OxideError; /// 重导出 `oxc_allocator::Allocator`:AST 节点的 bump 分配器,解析产物生命周期由其决定。 @@ -13,6 +15,59 @@ pub use oxc_ast::ast::*; /// 重导出 `oxc_span::Span`:源码中的位置区间。 pub use oxc_span::Span; +// 线程本地持久 bump 分配器:解析入口共享该分配器,chunk 内存按线程 +// 高水位保留复用,稳态解析不再走全局分配器。 +// +// 分配器存于 `UnsafeCell`:唯一可变操作 `reset()` 只在最外层(深度为零) +// 调用,彼时尚无共享引用存活;闭包执行期间只产生共享 `&Allocator` 引用 +// (重入不再 reset),共享引用可别名,无 `&`/`&mut` 混用。 +// 重入深度独立存于 `Cell`(`Copy` 类型内部可变),嵌套读写不产生借用冲突。 +thread_local! { + static PARSE_ALLOCATOR: UnsafeCell = UnsafeCell::new(Allocator::new()); + static PARSE_DEPTH: Cell = const { Cell::new(0) }; +} + +/// 重入深度守卫:出口(含 panic 展开)把深度减一,防深度泄漏。 +struct DepthGuard<'a>(&'a Cell); + +impl Drop for DepthGuard<'_> { + fn drop(&mut self) { + self.0.set(self.0.get() - 1); + } +} + +/// 在线程本地分配器上执行一次解析上下文:闭包内完成解析与编译, +/// AST 生命周期被闭包边界约束。 +/// +/// # 边界与前提 +/// - 闭包不得把 AST 逃逸出闭包边界:下一次顶层入口会 reset 分配器。 +/// - 闭包内重入本助手(嵌套解析)安全:深度非零时跳过 reset, +/// 嵌套解析共享同一分配器,AST 在外层退出前全部有效。 +/// +/// # 副作用 +/// - 深度为零时 reset 分配器(bump 指针归零,高水位 chunk 保留); +/// 重入深度入口加一、出口减一(含 panic 展开路径)。 +pub fn with_parse_allocator(f: F) -> R +where + F: for<'a> FnOnce(&'a Allocator) -> R, +{ + PARSE_DEPTH.with(|depth| { + let is_root = depth.get() == 0; + depth.set(depth.get() + 1); + let _guard = DepthGuard(depth); + PARSE_ALLOCATOR.with(|cell| { + let ptr = cell.get(); + // reset 只在最外层执行:此刻尚无共享引用存活,`&mut` 独占安全。 + if is_root { + unsafe { (*ptr).reset() }; + } + // 共享引用跨闭包存活;重入只再产生共享引用,无 `&mut` 混用。 + let alloc = unsafe { &*ptr }; + f(alloc) + }) + }) +} + /// 把 `source` 解析为 AST 根节点 `Program`,AST 分配在 `allocator` 中。 /// /// 使用 oxc 默认 `SourceType`(严格模式由语法自身决定)。返回 `Ok(program)` diff --git a/crates/oxide_test262/src/runner.rs b/crates/oxide_test262/src/runner.rs index 792adba1..c9d1ee46 100644 --- a/crates/oxide_test262/src/runner.rs +++ b/crates/oxide_test262/src/runner.rs @@ -145,30 +145,35 @@ fn run_test_inner( return TestResult::skip(path.to_path_buf(), e); } }; - let alloc = oxide_parser::Allocator::default(); - let program = match oxide_parser::parse_module(&alloc, &code) { - Ok(p) => p, - Err(errs) => { + // 线程本地分配器:解析与编译同闭包内完成,AST 生命周期被闭包边界约束。 + // 闭包返回模块列表或提前判定的结果,外层拆包:Ok 取模块,Err 直接返回结果。 + match oxide_parser::with_parse_allocator(|alloc| { + // 解析模块:解析失败按 negative 元数据判定 pass/fail,短路返回。 + let program = oxide_parser::parse_module(alloc, &code).map_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); + TestResult::pass(path.to_path_buf(), dur, msg) + } else { + TestResult::fail(path.to_path_buf(), dur, msg) } - return TestResult::fail(path.to_path_buf(), dur, msg); - } - }; - let mut loader = Test262ModuleLoader; - match compiler.compile_module(&program, path.to_string_lossy().as_ref(), &mut loader) { - Ok(m) => vec![Arc::new(m)], - Err(e) => { - let dur = start.elapsed().as_millis() as u64; - let msg = format!("compile error: {e}"); - match classify_compile_error(&msg, meta.negative.is_some(), no_skip) { - TestOutcome::Pass(m) => return TestResult::pass(path.to_path_buf(), dur, m), - TestOutcome::Fail(m) => return TestResult::fail(path.to_path_buf(), dur, m), - TestOutcome::Skip(m) => return TestResult::skip(path.to_path_buf(), m), + })?; + let mut loader = Test262ModuleLoader; + match compiler.compile_module(&program, path.to_string_lossy().as_ref(), &mut loader) { + Ok(m) => Ok(vec![Arc::new(m)]), + Err(e) => { + let dur = start.elapsed().as_millis() as u64; + let msg = format!("compile error: {e}"); + match classify_compile_error(&msg, meta.negative.is_some(), no_skip) { + TestOutcome::Pass(m) => Err(TestResult::pass(path.to_path_buf(), dur, m)), + TestOutcome::Fail(m) => Err(TestResult::fail(path.to_path_buf(), dur, m)), + TestOutcome::Skip(m) => Err(TestResult::skip(path.to_path_buf(), m)), + } } } + }) { + Ok(modules) => modules, + Err(result) => return result, } } else { // script 臂两模块方案:前缀模块按 includes 组合缓存(免每测试重解析重编译),