refactor: 动态编译改经 CompilerService 注入,oxide_vm 去编译器依赖

This commit is contained in:
2026-10-08 05:51:58 +00:00
parent 66735f802f
commit 4f9b6ab444
94 changed files with 459 additions and 67 deletions
+23 -19
View File
@@ -1,14 +1,33 @@
//! 动态编译:`Function` 构造器与脚本动态编译入口,及子树 flat_id 重编号。
//! 编译经 `Vm` 持有的 `CompilerService` 句柄完成(`self.compiler`),
//! 本 crate 生产代码不直接依赖 `oxide_compiler` / `oxide_parser`。
use std::sync::{Arc, OnceLock};
use oxide_bytecode::module::CompiledModule;
use oxide_bytecode::opcode::{self, OpCode};
use oxide_runtime_api::CompilerService;
use oxide_types::object::PropAttributes;
use oxide_types::value::JsValue;
use super::Vm;
/// 缺省 no-op 编译服务:两方法均返 `Err`(错误消息指向「未安装编译服务」)。
///
/// 供不需要动态编译的 `Vm` 实例(构造器缺省值);生产 entry points 与
/// 动态编译测试经 `set_compiler_service` 注入真实实现。
pub(crate) struct NoopCompilerService;
impl CompilerService for NoopCompilerService {
fn compile_script(&self, _source: &str) -> Result<CompiledModule, String> {
Err("dynamic compilation unavailable: no compiler service installed".to_string())
}
fn compile_eval_script(&self, _source: &str) -> Result<CompiledModule, String> {
Err("dynamic compilation unavailable: no compiler service installed".to_string())
}
}
impl Vm {
/// 动态编译函数(`Function` 构造器路径):把参数列表与函数体 wrap 成匿名函数
/// 源码,走完整编译链后取匿名函数模块追加进 VM 平表,返回对应函数对象。
@@ -57,15 +76,11 @@ impl Vm {
let params_str = params.join(", ");
let source = format!("{prefix} anonymous({params_str}\n) {{\n{body}\n}}");
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"))?;
// 动态路径源契约:调用方以源码域转义(`string_forge::source_escape`)
// 形态传源;正则字面量源切片经 `source_escape_to_key` 还原注入
// marker 后入池(编码源口径,区别于静态源的 `pool_key_plain`)。
let mut module = oxide_compiler::compiler::Compiler::new()
.with_source_encoded(true)
.compile(&program)?;
// 编译经注入的编译服务完成(parse 与 compile 串联在实现侧)。
let mut module = self.compiler.compile_script(&source)?;
let anonymous = module.sub_modules.remove(0);
// 形参数以编译结果为准:单个实参 "a,b,c" 拼接后解析为 3 个形参
// (ES 动态函数把非末位实参以逗号连接成参数串再解析)。
@@ -113,15 +128,9 @@ impl Vm {
/// # 副作用
/// - 扩展当前代际平表(`tables[current_gen]`)与常量缓存。
pub fn create_dynamic_script(&mut self, code: &str) -> Result<JsValue, String> {
let allocator = oxide_parser::Allocator::default();
let program = oxide_parser::parse(&allocator, code)
.map_err(|errs| errs.into_iter().map(|e| e.message).collect::<Vec<_>>().join("\n"))?;
// eval 脚本:顶层 var/function 声明落全局属性 configurable:true。
// 动态路径源契约同 `create_dynamic_function`(源码域转义形态传源)。
let module = oxide_compiler::compiler::Compiler::new()
.with_eval_script(true)
.with_source_encoded(true)
.compile(&program)?;
let module = self.compiler.compile_eval_script(code)?;
// 根模块 flat_id=0 传 base+1,重编号后落 base(避开 sub_module_index()==0
// 守卫)。make_mut 彼时平表 Arc 强引用唯一持有者是本表,原地扩展不分叉。
let table = self.current_table_mut();
@@ -157,15 +166,10 @@ impl Vm {
/// # 副作用
/// - 扩展当前代际平表(`tables[current_gen]`)与常量缓存。
pub fn create_plain_dynamic_script(&mut self, code: &str) -> Result<JsValue, String> {
let allocator = oxide_parser::Allocator::default();
let program = oxide_parser::parse(&allocator, code)
.map_err(|errs| errs.into_iter().map(|e| e.message).collect::<Vec<_>>().join("\n"))?;
// 普通脚本:顶层 var/function 声明落全局属性 configurable:false,
// let/const 落全局词法环境。动态路径源契约同 `create_dynamic_function`
// (源码域转义形态传源)。
let module = oxide_compiler::compiler::Compiler::new()
.with_source_encoded(true)
.compile(&program)?;
let module = self.compiler.compile_script(code)?;
// 根模块 flat_id=0 传 base+1,重编号后落 base(避开 sub_module_index()==0
// 守卫)。make_mut 彼时平表 Arc 强引用唯一持有者是本表,原地扩展不分叉。
let table = self.current_table_mut();
+19
View File
@@ -37,6 +37,7 @@ mod props;
mod tables;
mod vmhost;
pub(crate) use dynamic::NoopCompilerService;
pub(crate) use frames::FrameArgs;
pub use frames::{CallFrame, Completion, ForInIter, FrameContinuation, TryHandler};
pub(crate) use gc_hooks::RootGroup;
@@ -175,6 +176,12 @@ pub struct Vm {
pub(crate) active_immutables: *const [JsValue],
pub(crate) frames: SmallVec<[CallFrame; 16]>,
pub(crate) kernel_core: Arc<KernelCore>,
/// 动态编译服务句柄:`Function` 构造器 / eval / `$262.evalScript` 经它编译
/// 源码(`dynamic.rs` 三方法)。缺省为 no-op stub(返 `Err`),生产 entry
/// points 与动态编译测试经 `set_compiler_service` 注入真实实现。
/// `Arc<dyn CompilerService>` 因 trait `Send + Sync` 而 Send + Sync,
/// 不破坏 `unsafe impl Send for Vm`。
pub(crate) compiler: Arc<dyn oxide_runtime_api::CompilerService>,
/// 每 VM 的 realm 组合:内核会话(builtin world 与 global 对象)、
/// session GC 簿记与 10 个内建原型槽(见 `realm` 模块)。以 `Arc` 持有:
/// 可变组经 `RefCell` 内部可变性在 `&Arc<Realm>` 下改写,teardown 归
@@ -445,6 +452,18 @@ impl Vm {
&self.kernel_core
}
/// 注入动态编译服务(`Function` 构造器 / eval / `$262.evalScript` 的编译面)。
///
/// # 边界与前提
/// - 构造后缺省为 no-op stub(动态编译返 `Err`);需要动态编译的调用方
/// (生产 entry points 与动态编译测试)须在首次动态编译前注入真实实现。
///
/// # 副作用
/// - 替换 `compiler` 句柄(`Arc` 克隆,无其他状态变更)。
pub fn set_compiler_service(&mut self, service: Arc<dyn oxide_runtime_api::CompilerService>) {
self.compiler = service;
}
/// 只读访问当前 session(builtin world 与 global object)。
///
/// 返回 `Ref` 守卫(session 入 `RefCell` 后无法再给稳定 `&`):调用方
+40 -21
View File
@@ -5,6 +5,7 @@ use std::time::Duration;
use crate::vm::Vm;
use crate::{vm_debug, vm_trace, vm_warn};
use oxide_kernel::kernel::KernelCore;
use oxide_runtime_api::CompilerService;
struct VmPoolInner {
available: Vec<Vm>,
@@ -74,6 +75,9 @@ impl PoolCounters {
/// 归还(带 5 秒超时强制扩容兜底)。线程安全,可供多线程并发领取。
pub struct VmPool {
kernel_core: Arc<KernelCore>,
/// 动态编译服务句柄:构造期定、此后不可变,`new_vm` 逐个注入每个 Vm
/// (预热与增长路径同口径,无「预热后补注入」时序问题)。
compiler: Arc<dyn CompilerService>,
inner: Mutex<VmPoolInner>,
condvar: Condvar,
max_size: Option<usize>,
@@ -94,9 +98,12 @@ impl VmPool {
/// 创建 VM 池并同步预热 `min_size` 个 Vm(数量受 `max_size` 钳制),
/// 首次 `spawn` 直接命中池。`max_size` 为池上限,`None` 表示不限。
///
/// `compiler` 为池内全部 Vm 的动态编译服务(构造期定、此后不可变)。
/// 计数器为本池私有(不与其他池共享)。
pub fn new(kernel_core: Arc<KernelCore>, min_size: usize, max_size: Option<usize>) -> Arc<Self> {
Self::with_counters(kernel_core, min_size, max_size, PoolCounters::shared())
pub fn new(
kernel_core: Arc<KernelCore>, compiler: Arc<dyn CompilerService>, min_size: usize, max_size: Option<usize>,
) -> Arc<Self> {
Self::with_counters(kernel_core, compiler, min_size, max_size, PoolCounters::shared())
}
/// 创建共享外部聚合计数器的 VM 池:多个池的原子增减累加到同一聚合,
@@ -110,15 +117,18 @@ impl VmPool {
/// # 边界与前提
/// - 计数器通常经 `PoolCounters::shared` 跨线程共享;池本体可跨线程移动
/// (Send/Sync),worker 经 spawn 独占借出 Vm。
/// - `compiler` 在构造期固化:预热与增长路径经 `new_vm` 逐个注入同一服务。
///
/// # 副作用
/// - 同步创建 `min_size` 个 Vm。
pub fn with_counters(
kernel_core: Arc<KernelCore>, min_size: usize, max_size: Option<usize>, counters: Arc<PoolCounters>,
kernel_core: Arc<KernelCore>, compiler: Arc<dyn CompilerService>, min_size: usize, max_size: Option<usize>,
counters: Arc<PoolCounters>,
) -> Arc<Self> {
let warm = min_size.min(max_size.unwrap_or(min_size));
let pool = Arc::new(Self {
kernel_core: Arc::clone(&kernel_core),
compiler: Arc::clone(&compiler),
inner: Mutex::new(VmPoolInner {
available: Vec::new(),
total_count: 0,
@@ -129,7 +139,7 @@ impl VmPool {
});
let mut inner = pool.inner.lock().unwrap();
for _ in 0..warm {
inner.available.push(Self::new_vm(&kernel_core));
inner.available.push(pool.new_vm());
inner.total_count += 1;
}
drop(inner);
@@ -138,12 +148,15 @@ impl VmPool {
pool
}
fn new_vm(core: &Arc<KernelCore>) -> Vm {
Vm::with_kernel_core(Arc::clone(core))
/// 新建一个 Vm 并注入本池的动态编译服务(预热与增长路径共用)。
fn new_vm(&self) -> Vm {
let mut vm = Vm::with_kernel_core(Arc::clone(&self.kernel_core));
vm.set_compiler_service(Arc::clone(&self.compiler));
vm
}
fn replace_vm(&self) -> Vm {
Self::new_vm(&self.kernel_core)
self.new_vm()
}
/// 从池中借出一个 VM:优先复用空闲实例,否则在池未满时新建,池满则阻塞等待归还。
@@ -173,7 +186,7 @@ impl VmPool {
drop(inner);
self.counters.total.fetch_add(1, Ordering::Relaxed);
self.counters.record_borrow();
let vm = Self::new_vm(&self.kernel_core);
let vm = self.new_vm();
return VmGuard {
vm: Some(vm),
pool: Arc::clone(self),
@@ -189,7 +202,7 @@ impl VmPool {
drop(inner);
self.counters.total.fetch_add(1, Ordering::Relaxed);
self.counters.record_borrow();
let vm = Self::new_vm(&self.kernel_core);
let vm = self.new_vm();
return VmGuard {
vm: Some(vm),
pool: Arc::clone(self),
@@ -291,16 +304,22 @@ impl Drop for VmGuard {
#[cfg(test)]
mod tests {
use super::*;
use oxide_compiler::DefaultCompilerService;
use oxide_kernel::kernel::KernelConfig;
fn test_kernel() -> Arc<KernelCore> {
KernelCore::new(KernelConfig::minimal())
}
/// 测试池的编译服务(池测试不 exercise 动态编译,缺省实现即可)。
fn test_compiler() -> Arc<dyn CompilerService> {
Arc::new(DefaultCompilerService)
}
#[test]
fn test_pool_spawn_returns_guard() {
let kernel = test_kernel();
let pool = VmPool::new(kernel, 1, None);
let pool = VmPool::new(kernel, test_compiler(), 1, None);
let guard = pool.spawn();
drop(guard);
}
@@ -308,7 +327,7 @@ mod tests {
#[test]
fn test_pool_recycle_on_drop() {
let kernel = test_kernel();
let pool = VmPool::new(kernel, 1, None);
let pool = VmPool::new(kernel, test_compiler(), 1, None);
let guard = pool.spawn();
drop(guard);
let guard2 = pool.spawn();
@@ -318,7 +337,7 @@ mod tests {
#[test]
fn test_pool_grows_if_empty() {
let kernel = test_kernel();
let pool = VmPool::new(Arc::clone(&kernel), 1, Some(3));
let pool = VmPool::new(Arc::clone(&kernel), test_compiler(), 1, Some(3));
let g1 = pool.spawn();
let g2 = pool.spawn();
drop(g1);
@@ -328,7 +347,7 @@ mod tests {
#[test]
fn test_pool_warms_min_size_on_new() {
let kernel = test_kernel();
let pool = VmPool::new(kernel, 2, None);
let pool = VmPool::new(kernel, test_compiler(), 2, None);
{
let inner = pool.inner.lock().unwrap();
assert_eq!(inner.available.len(), 2);
@@ -347,7 +366,7 @@ mod tests {
#[test]
fn test_pool_warmup_clamped_by_max_size() {
let kernel = test_kernel();
let pool = VmPool::new(kernel, 2, Some(1));
let pool = VmPool::new(kernel, test_compiler(), 2, Some(1));
let inner = pool.inner.lock().unwrap();
assert_eq!(inner.available.len(), 1);
assert_eq!(inner.total_count, 1);
@@ -357,8 +376,8 @@ mod tests {
fn test_with_counters_shared_aggregate() {
let kernel = test_kernel();
let counters = PoolCounters::shared();
let pool_a = VmPool::with_counters(Arc::clone(&kernel), 1, Some(4), Arc::clone(&counters));
let pool_b = VmPool::with_counters(Arc::clone(&kernel), 2, Some(4), Arc::clone(&counters));
let pool_a = VmPool::with_counters(Arc::clone(&kernel), test_compiler(), 1, Some(4), Arc::clone(&counters));
let pool_b = VmPool::with_counters(Arc::clone(&kernel), test_compiler(), 2, Some(4), Arc::clone(&counters));
// 预热后聚合等于两池之和:总数 1 + 2 = 3,全部空闲。
assert_eq!(counters.total(), 3, "聚合总数应为两池预热之和");
@@ -383,7 +402,7 @@ mod tests {
#[test]
fn test_peak_count_single_thread_exact() {
let kernel = test_kernel();
let pool = VmPool::new(kernel, 2, None);
let pool = VmPool::new(kernel, test_compiler(), 2, None);
assert_eq!(pool.peak_count(), 0, "从未借出的池峰值应为零");
let g1 = pool.spawn();
@@ -401,8 +420,8 @@ mod tests {
fn test_peak_count_shared_aggregate() {
let kernel = test_kernel();
let counters = PoolCounters::shared();
let pool_a = VmPool::with_counters(Arc::clone(&kernel), 1, Some(4), Arc::clone(&counters));
let pool_b = VmPool::with_counters(Arc::clone(&kernel), 1, Some(4), Arc::clone(&counters));
let pool_a = VmPool::with_counters(Arc::clone(&kernel), test_compiler(), 1, Some(4), Arc::clone(&counters));
let pool_b = VmPool::with_counters(Arc::clone(&kernel), test_compiler(), 1, Some(4), Arc::clone(&counters));
let g1 = pool_a.spawn();
let g2 = pool_b.spawn();
@@ -427,7 +446,7 @@ mod tests {
std::thread::spawn(move || {
// 每线程自建池、共享同一聚合计数器(Vm 经 unsafe impl Send 可
// 跨线程,本测试仍按每线程自有池验证计数器聚合)。
let pool = VmPool::with_counters(kernel, 0, Some(8), counters);
let pool = VmPool::with_counters(kernel, test_compiler(), 0, Some(8), counters);
for _ in 0..50 {
let guard = pool.spawn();
drop(guard);
@@ -447,7 +466,7 @@ mod tests {
#[test]
fn test_collect_idle_gc_returns_idle_count() {
let kernel = test_kernel();
let pool = VmPool::new(kernel, 1, None);
let pool = VmPool::new(kernel, test_compiler(), 1, None);
let guard = pool.spawn();
drop(guard);
assert_eq!(pool.collect_idle_gc(), 1, "归还一个空闲 VM 后收集数应为 1");
+6
View File
@@ -68,6 +68,9 @@ impl Vm {
active_immutables: std::ptr::slice_from_raw_parts(std::ptr::null(), 0),
frames: smallvec::SmallVec::new(),
kernel_core: core,
// 缺省 no-op 编译服务:动态编译返 Err,生产 entry points 与
// 动态编译测试经 set_compiler_service 注入真实实现。
compiler: Arc::new(crate::vm::NoopCompilerService),
realm: Arc::new(Realm {
realm_id,
session: RefCell::new(session),
@@ -221,6 +224,9 @@ impl Vm {
active_immutables: std::ptr::slice_from_raw_parts(std::ptr::null(), 0),
frames: smallvec::SmallVec::new(),
kernel_core: core,
// 缺省 no-op 编译服务:动态编译返 Err,生产 entry points 与
// 动态编译测试经 set_compiler_service 注入真实实现。
compiler: Arc::new(crate::vm::NoopCompilerService),
realm: Arc::new(Realm {
realm_id,
session: RefCell::new(session),
+12
View File
@@ -1,6 +1,8 @@
//! vm_support 内联测试:全量重置状态清除与全局重建、分配上限、生成器与 BigInt 存活、动态编译及源文本断言行为。
use super::*;
use oxide_compiler::DefaultCompilerService;
fn global_prop(vm: &Vm, name: &str) -> JsValue {
global_prop_opt(vm, name).expect("global slot should exist")
}
@@ -499,6 +501,7 @@ fn immutables_filled_once_per_module() {
#[test]
fn dynamic_function_basic_arity() {
let mut vm = Vm::new();
vm.set_compiler_service(Arc::new(DefaultCompilerService));
// 与等价静态函数返回值逐位一致(引擎函数调用统一产 Double 数值)。
let expected = run_source(&mut vm, "function f(a,b){return a+b} f(3,4)");
let result = run_source(&mut vm, "new Function('a','b','return a+b')(3,4)");
@@ -508,6 +511,7 @@ fn dynamic_function_basic_arity() {
#[test]
fn dynamic_function_called_without_new() {
let mut vm = Vm::new();
vm.set_compiler_service(Arc::new(DefaultCompilerService));
let expected = run_source(&mut vm, "function f(a,b){return a*b} f(6,7)");
let result = run_source(&mut vm, "Function('a','b','return a*b')(6,7)");
assert_eq!(result, expected);
@@ -516,12 +520,14 @@ fn dynamic_function_called_without_new() {
#[test]
fn dynamic_function_empty_body_returns_undefined() {
let mut vm = Vm::new();
vm.set_compiler_service(Arc::new(DefaultCompilerService));
assert!(run_source(&mut vm, "Function()()").is_undefined());
}
#[test]
fn dynamic_function_syntax_error_throws_syntax_error() {
let mut vm = Vm::new();
vm.set_compiler_service(Arc::new(DefaultCompilerService));
let result = run_source(&mut vm, "try{new Function('return {{')}catch(e){e.name}");
assert!(result.is_string());
assert_eq!(vm.lookup_str(result).as_deref(), Some("SyntaxError"));
@@ -530,6 +536,7 @@ fn dynamic_function_syntax_error_throws_syntax_error() {
#[test]
fn dynamic_function_nested_closure_renumbering() {
let mut vm = Vm::new();
vm.set_compiler_service(Arc::new(DefaultCompilerService));
// 匿名函数体声明嵌套函数 g,返回值是引用 g 的闭包:验证子树 flat_id 重编号
// 与 CREATE_CLOSURE imm16 重写后嵌套调用仍指向正确的子模块。
let expected = run_source(
@@ -546,6 +553,7 @@ fn dynamic_function_nested_closure_renumbering() {
#[test]
fn dynamic_function_multiple_in_one_run() {
let mut vm = Vm::new();
vm.set_compiler_service(Arc::new(DefaultCompilerService));
// 同一 run 内连续创建多个动态函数:验证 base 偏移累计正确。
let expected = run_source(
&mut vm,
@@ -561,6 +569,7 @@ fn dynamic_function_multiple_in_one_run() {
#[test]
fn dynamic_function_name_and_length() {
let mut vm = Vm::new();
vm.set_compiler_service(Arc::new(DefaultCompilerService));
let name = run_source(&mut vm, "var f=new Function('a','b','return a'); f.name");
assert!(name.is_string());
assert_eq!(vm.lookup_str(name).as_deref(), Some("anonymous"));
@@ -571,6 +580,7 @@ fn dynamic_function_name_and_length() {
#[test]
fn dynamic_function_comma_split_params_count() {
let mut vm = Vm::new();
vm.set_compiler_service(Arc::new(DefaultCompilerService));
// 单个实参 "a,b,c" 拼接解析为 3 个形参,length 应为解析后的形参数。
let result = run_source(&mut vm, "new Function('a,b,c','null').length");
assert_eq!(result, JsValue::int(3));
@@ -579,6 +589,7 @@ fn dynamic_function_comma_split_params_count() {
#[test]
fn dynamic_function_name_and_length_attributes() {
let mut vm = Vm::new();
vm.set_compiler_service(Arc::new(DefaultCompilerService));
// length/name 为不可写、不可枚举、可配置的数据属性。
let attrs = run_source(
&mut vm,
@@ -595,6 +606,7 @@ fn dynamic_function_name_and_length_attributes() {
#[test]
fn dynamic_function_rethrows_to_string_exception() {
let mut vm = Vm::new();
vm.set_compiler_service(Arc::new(DefaultCompilerService));
// 形参 ToString 回调抛出的原始值须原样传播,而非包成 TypeError。
let result = run_source(&mut vm, "try{new Function({toString:function(){throw 7}})}catch(e){e}");
assert_eq!(result, JsValue::int(7));