From 349164f2442b9f14622ed35bc2eba9dd46c21b67 Mon Sep 17 00:00:00 2001 From: pjh456 <147148383@qq.com> Date: Thu, 8 Oct 2026 09:00:31 +0000 Subject: [PATCH] =?UTF-8?q?refactor:=20=E4=BB=8E=20run=20=E6=8A=BD=20execu?= =?UTF-8?q?te=5Ftask=20=E4=B8=8E=20drain=5Fmicrotasks=20=E4=B8=A4=E5=8E=9F?= =?UTF-8?q?=E8=AF=AD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Cargo.lock | 38 +++++------ Cargo.toml | 2 +- crates/oxide_vm/src/promise/jobs.rs | 6 +- crates/oxide_vm/src/promise/mod.rs | 3 +- crates/oxide_vm/src/vm/mod.rs | 3 +- crates/oxide_vm/src/vm_runtime.rs | 99 ++++++++++++++++++++++++++++- 6 files changed, 125 insertions(+), 26 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 9e8c1774..e92342c1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1160,7 +1160,7 @@ dependencies = [ [[package]] name = "oxide_builtins" -version = "0.8.78" +version = "0.8.79" dependencies = [ "chrono", "fast-float", @@ -1185,7 +1185,7 @@ dependencies = [ [[package]] name = "oxide_bytecode" -version = "0.8.78" +version = "0.8.79" dependencies = [ "num-bigint", "oxide_log", @@ -1193,7 +1193,7 @@ dependencies = [ [[package]] name = "oxide_cfg" -version = "0.8.78" +version = "0.8.79" dependencies = [ "oxide_bytecode", "oxide_emit", @@ -1204,7 +1204,7 @@ dependencies = [ [[package]] name = "oxide_cli" -version = "0.8.78" +version = "0.8.79" dependencies = [ "ansi_term", "bumpalo", @@ -1226,7 +1226,7 @@ dependencies = [ [[package]] name = "oxide_code_cache" -version = "0.8.78" +version = "0.8.79" dependencies = [ "lru", "oxide_bytecode", @@ -1235,7 +1235,7 @@ dependencies = [ [[package]] name = "oxide_compiler" -version = "0.8.78" +version = "0.8.79" dependencies = [ "oxide_bytecode", "oxide_cfg", @@ -1255,7 +1255,7 @@ dependencies = [ [[package]] name = "oxide_dce" -version = "0.8.78" +version = "0.8.79" dependencies = [ "oxide_bytecode", "oxide_cfg", @@ -1269,7 +1269,7 @@ dependencies = [ [[package]] name = "oxide_emit" -version = "0.8.78" +version = "0.8.79" dependencies = [ "num-bigint", "oxc_allocator", @@ -1284,7 +1284,7 @@ dependencies = [ [[package]] name = "oxide_engine" -version = "0.8.78" +version = "0.8.79" dependencies = [ "oxide_bytecode", "oxide_compiler", @@ -1296,7 +1296,7 @@ dependencies = [ [[package]] name = "oxide_ir" -version = "0.8.78" +version = "0.8.79" dependencies = [ "oxide_bytecode", "oxide_log", @@ -1305,7 +1305,7 @@ dependencies = [ [[package]] name = "oxide_kernel" -version = "0.8.78" +version = "0.8.79" dependencies = [ "bumpalo", "dashmap", @@ -1319,7 +1319,7 @@ dependencies = [ [[package]] name = "oxide_liveness" -version = "0.8.78" +version = "0.8.79" dependencies = [ "oxide_bytecode", "oxide_cfg", @@ -1331,7 +1331,7 @@ dependencies = [ [[package]] name = "oxide_log" -version = "0.8.78" +version = "0.8.79" dependencies = [ "tracing", "tracing-appender", @@ -1340,7 +1340,7 @@ dependencies = [ [[package]] name = "oxide_parser" -version = "0.8.78" +version = "0.8.79" dependencies = [ "oxc_allocator", "oxc_ast", @@ -1355,7 +1355,7 @@ dependencies = [ [[package]] name = "oxide_regalloc" -version = "0.8.78" +version = "0.8.79" dependencies = [ "oxide_bytecode", "oxide_cfg", @@ -1371,7 +1371,7 @@ dependencies = [ [[package]] name = "oxide_runtime_api" -version = "0.8.78" +version = "0.8.79" dependencies = [ "num-bigint", "num-traits", @@ -1383,7 +1383,7 @@ dependencies = [ [[package]] name = "oxide_test262" -version = "0.8.78" +version = "0.8.79" dependencies = [ "oxide_bytecode", "oxide_compiler", @@ -1401,7 +1401,7 @@ dependencies = [ [[package]] name = "oxide_types" -version = "0.8.78" +version = "0.8.79" dependencies = [ "bumpalo", "dashmap", @@ -1414,7 +1414,7 @@ dependencies = [ [[package]] name = "oxide_vm" -version = "0.8.78" +version = "0.8.79" dependencies = [ "bumpalo", "chrono", diff --git a/Cargo.toml b/Cargo.toml index bdab62e4..54c607ff 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,7 +23,7 @@ members = [ ] [workspace.package] -version = "0.8.78" +version = "0.8.79" edition = "2021" license = "MIT" rust-version = "1.80" diff --git a/crates/oxide_vm/src/promise/jobs.rs b/crates/oxide_vm/src/promise/jobs.rs index 2ed4ec55..66a87afe 100644 --- a/crates/oxide_vm/src/promise/jobs.rs +++ b/crates/oxide_vm/src/promise/jobs.rs @@ -11,15 +11,17 @@ use super::{Microtask, MAX_DRAIN_JOBS}; impl Vm { /// drain 微任务队列:FIFO 逐条处理直到清空或达到上限。 /// + /// 调用时机:`run()` 末尾与事件循环每个 turn 边界。 + /// /// # 副作用 /// - 任务内调用 JS 回调(`call_function_sync`),可能入队新任务。 /// - 单任务抛错不中断 drain(未处理拒绝按规范不可见)。 - pub(crate) fn drain_job_queue(&mut self) { + pub fn drain_microtasks(&mut self) { let mut count = 0usize; while let Some(job) = self.job_queue.pop_front() { count += 1; if count > MAX_DRAIN_JOBS { - vm_warn!("drain_job_queue: exceeded {MAX_DRAIN_JOBS} microtasks, aborting"); + vm_warn!("drain_microtasks: exceeded {MAX_DRAIN_JOBS} microtasks, aborting"); break; } self.run_microtask(job); diff --git a/crates/oxide_vm/src/promise/mod.rs b/crates/oxide_vm/src/promise/mod.rs index a6d72329..18f6ea71 100644 --- a/crates/oxide_vm/src/promise/mod.rs +++ b/crates/oxide_vm/src/promise/mod.rs @@ -3,7 +3,8 @@ //! Promise 对象把 `PromiseState` 状态盒(Box)挂在 `native_data`;resolve/reject //! 是携带目标 promise 的 native 闭包函数(函数对象 prop 存 promise 引用)。 //! 反应(reaction)与 thenable 委托以 `Microtask` 入队 `Vm::job_queue`, -//! 由 `run()` 末尾的 drain 循环 FIFO 执行。所有盒内 JsValue 由 session GC +//! 由 `drain_microtasks` 在 `run()` 末尾与事件循环每个 turn 边界 FIFO 执行。 +//! 所有盒内 JsValue 由 session GC //! 经 Promise 对象边追踪(见 `promise_native_edges` / `drop_promise_native`)。 use oxide_types::object::JsObject; diff --git a/crates/oxide_vm/src/vm/mod.rs b/crates/oxide_vm/src/vm/mod.rs index 136427d9..e4204288 100644 --- a/crates/oxide_vm/src/vm/mod.rs +++ b/crates/oxide_vm/src/vm/mod.rs @@ -201,7 +201,8 @@ pub struct Vm { /// f64→string 十六槽 last-value 缓存的值(session 串,经 `for_each_value` /// 登记为 GC 根;`full_reset` 在 session 串释放前清空)。 pub(crate) number_to_string_cache_vals: [JsValue; 16], - /// 微任务队列(Promise reactions / thenable 委托),`run()` 末尾 FIFO drain。 + /// 微任务队列(Promise reactions / thenable 委托),由 `drain_microtasks` + /// 在 `run()` 末尾与事件循环每个 turn 边界 FIFO drain。 pub(crate) job_queue: VecDeque, /// Atomics.waitAsync waiter 表:键 = (缓冲对象指针, 元素字节偏移),值 = /// 登记的 promise FIFO。键取登记时刻视图的 `buffer` 现指针(同 run 无 GC 时 diff --git a/crates/oxide_vm/src/vm_runtime.rs b/crates/oxide_vm/src/vm_runtime.rs index 9531fd0c..85fd3f44 100644 --- a/crates/oxide_vm/src/vm_runtime.rs +++ b/crates/oxide_vm/src/vm_runtime.rs @@ -647,6 +647,23 @@ impl Vm { self.tables.retain(|gen, _| live_gens.contains(gen)); } + /// 执行一个 JS 任务:闭包是任务体,本方法执行它并原样返回结果。 + /// + /// 任务体在 `run()` 是 `dispatch` 主循环,在 worker 消息是单次函数调用。 + /// 本方法是任务边界原语,不排空微任务队列(排空时机由事件循环决定)。 + /// + /// # 边界与前提 + /// - 闭包返回 `Err`(未捕获异常)时原样上交,不吞并、不改写。 + /// + /// # 副作用 + /// - 任务体对 `self` 的全部改写(寄存器 / 帧栈 / 对象表 / 异常侧通道)原样保留。 + pub fn execute_task( + &mut self, + task: impl FnOnce(&mut Self) -> Result, + ) -> Result { + task(self) + } + /// 加载并执行一个已编译模块,返回模块顶层执行结果或未捕获异常消息。 /// /// 模块以 `Arc` 与调用方共享(如 CodeForge 缓存条目):平表装载只做 @@ -701,9 +718,9 @@ impl Vm { self.top_level_this = this_val; self.regs[254] = this_val; - let result = self.dispatch(); + let result = self.execute_task(|vm| vm.dispatch()); // 顶层执行结束后 drain 微任务队列:Promise reactions 与 thenable 委托在此执行。 - self.drain_job_queue(); + self.drain_microtasks(); // 指令周期采样:run 末聚合本 run 的样本并输出 top-K 直方图(关闭时零开销 // 短路)。放 dispatch 返回后而非循环内:嵌套 dispatch 会多次返回,直方图 // 只应输出一次。 @@ -1004,4 +1021,82 @@ mod tests { assert_eq!(vm.regs[254], JsValue::float(254.0)); assert_eq!(vm.regs[255], JsValue::float(255.0)); } + + fn global_value(vm: &mut Vm, name: &str) -> JsValue { + let key = vm.kernel_core.perm_interner().intern(name).0; + let session = vm.realm.session.borrow(); + let global = session.global_object(); + let pos = vm + .kernel_core + .shape_forge() + .lookup_position(global.shape_id(), key) + .expect("global slot"); + global.get_prop_at(pos) + } + + fn as_number(v: &JsValue) -> f64 { + if v.is_int() { + v.as_int() as f64 + } else { + v.as_double() + } + } + + #[test] + fn execute_task_returns_closure_result() { + // 任务体为单次 call_function_sync 调用,execute_task 原样返回闭包结果。 + let mut vm = Vm::new(); + let module = Arc::new(compile("function f() { return 42; }")); + vm.run(&module).expect("run"); + let f = global_value(&mut vm, "f"); + let result = vm.execute_task(|vm| vm.call_function_sync(f, JsValue::undefined(), &[])); + assert_eq!(as_number(&result.expect("execute_task")), 42.0); + } + + #[test] + fn execute_task_propagates_closure_error() { + // 任务体抛未捕获异常:execute_task 原样上交 Err,last_uncaught_value 侧通道按既有语义填充。 + let mut vm = Vm::new(); + let module = Arc::new(compile("function f() { throw new Error('boom'); }")); + vm.run(&module).expect("run"); + let f = global_value(&mut vm, "f"); + let result = vm.execute_task(|vm| vm.call_function_sync(f, JsValue::undefined(), &[])); + assert!(result.is_err(), "未捕获异常须以 Err 上交"); + assert!(vm.last_uncaught_value.is_some(), "last_uncaught_value 侧通道须填充"); + } + + #[test] + fn drain_microtasks_runs_promise_reactions() { + // 任务体建 promise 并挂 .then:execute_task 不排空微任务队列,反应留在队列, + // drain_microtasks 执行之,侧效应发生。 + let mut vm = Vm::new(); + let module = Arc::new(compile( + "var done = false; \ + function make() { Promise.resolve(1).then(function (x) { done = true; }); }", + )); + vm.run(&module).expect("run"); + let make = global_value(&mut vm, "make"); + vm.execute_task(|vm| vm.call_function_sync(make, JsValue::undefined(), &[])) + .expect("execute_task"); + assert!(!vm.job_queue.is_empty(), "反应应已入队"); + vm.drain_microtasks(); + assert!(vm.job_queue.is_empty(), "drain 后队列应清空"); + assert_eq!( + global_value(&mut vm, "done"), + JsValue::bool(true), + "promise 反应应已置 done = true" + ); + } + + #[test] + fn drain_microtasks_empty_queue_idempotent() { + // run 后队列为空,连续两次 drain_microtasks 无副作用。 + let mut vm = Vm::new(); + let module = Arc::new(compile("var x = 1;")); + vm.run(&module).expect("run"); + assert!(vm.job_queue.is_empty()); + vm.drain_microtasks(); + vm.drain_microtasks(); + assert!(vm.job_queue.is_empty()); + } }