refactor: 从 run 抽 execute_task 与 drain_microtasks 两原语

This commit is contained in:
2026-10-08 09:00:31 +00:00
parent 18d1c63cd3
commit 349164f244
6 changed files with 125 additions and 26 deletions
Generated
+19 -19
View File
@@ -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",
+1 -1
View File
@@ -23,7 +23,7 @@ members = [
]
[workspace.package]
version = "0.8.78"
version = "0.8.79"
edition = "2021"
license = "MIT"
rust-version = "1.80"
+4 -2
View File
@@ -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);
+2 -1
View File
@@ -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;
+2 -1
View File
@@ -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<crate::promise::Microtask>,
/// Atomics.waitAsync waiter 表:键 = (缓冲对象指针, 元素字节偏移),值 =
/// 登记的 promise FIFO。键取登记时刻视图的 `buffer` 现指针(同 run 无 GC 时
+97 -2
View File
@@ -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<JsValue, String>,
) -> Result<JsValue, String> {
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());
}
}