Files
OxideJS-New/crates/oxide_vm/src/vm_runtime.rs
T

1103 lines
52 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
use std::collections::HashMap;
use std::sync::{Arc, OnceLock};
use oxide_bytecode::module::CompiledModule;
use oxide_bytecode::opcode::OpCode;
use crate::vm::{CallFrame, FrameArgs, FrameContinuation, InlineSyncState, TableGen, Vm};
use crate::{vm_debug, vm_info, vm_trace, vm_warn};
use oxide_types::object::{Cell, JsObject};
use oxide_types::value::JsValue;
/// 按 `flat_id` 下标收集整棵子模块树为平表,供 `run()` 装载。
///
/// 顶层与子模块一律 `Arc` 共享:只做 Arc::clone(O(1) 引用计数),run 期
/// 零模块拷贝。唯一可写面 = bytecode 缓冲,经 `bytecode_mut` 的 COW 写时
/// 复制——dispatch 期平表与 `Vm.bytecode` 双持(refcount ≥ 2),写必落在
/// 私有拷贝上,宿主侧共享缓冲永不被写。
fn collect_flat_modules(module: &Arc<CompiledModule>) -> Vec<Arc<CompiledModule>> {
let mut out: Vec<Option<Arc<CompiledModule>>> = Vec::new();
let top = Arc::clone(module);
place_flat(&top, &mut out);
out.into_iter().map(|m| m.expect("flat_id slot must be filled")).collect()
}
fn place_flat(module: &Arc<CompiledModule>, out: &mut Vec<Option<Arc<CompiledModule>>>) {
let idx = module.flat_id as usize;
if idx >= out.len() {
out.resize(idx + 1, None);
}
out[idx] = Some(Arc::clone(module));
for sub in &module.sub_modules {
place_flat(sub, out);
}
}
/// save 方向的字段取值表达式(字段名由调用方以 ident 位置提供,本宏只产出值)。
/// `regs` 取窗口副本:值来自 `save_inline_state` 预填充的 `window_regs` 局部缓冲
/// (经宏参数注入,绕开 macro hygiene),从池中取出后即 move 进 `InlineSyncState`。
macro_rules! inline_save_field {
($recv:ident, $window_regs:ident, regs, boxed_window) => {
std::mem::take(&mut $window_regs).into_boxed_slice()
};
($recv:ident, $window_regs:ident, saved_this, copy) => {
$recv.regs[254]
};
($recv:ident, $window_regs:ident, saved_new_target, copy) => {
$recv.regs[255]
};
($recv:ident, $window_regs:ident, pc, copy) => {
$recv.pc
};
($recv:ident, $window_regs:ident, bytecode, move_field) => {
std::mem::take(&mut $recv.bytecode)
};
($recv:ident, $window_regs:ident, active_immutables, copy) => {
$recv.active_immutables
};
($recv:ident, $window_regs:ident, active_reg_limit, copy) => {
$recv.active_reg_limit
};
($recv:ident, $window_regs:ident, root_reg_limit, copy) => {
$recv.root_reg_limit
};
($recv:ident, $window_regs:ident, try_stack, move_field) => {
std::mem::take(&mut $recv.try_stack)
};
($recv:ident, $window_regs:ident, frames, frames_values) => {
std::mem::take(&mut $recv.frames)
};
($recv:ident, $window_regs:ident, exception_value, opt_take) => {
$recv.exception_value.take()
};
($recv:ident, $window_regs:ident, pending_exception, opt_take) => {
$recv.pending_exception.take()
};
($recv:ident, $window_regs:ident, pending_error_kind, opt_take) => {
$recv.pending_error_kind.take()
};
($recv:ident, $window_regs:ident, pending_completion, opt_copy) => {
$recv.pending_completion
};
($recv:ident, $window_regs:ident, for_in_iters, for_in_keys) => {
std::mem::take(&mut $recv.iters.for_in_iters)
};
($recv:ident, $window_regs:ident, for_of_iters, iter_take) => {
std::mem::take(&mut $recv.iters.for_of_iters)
};
($recv:ident, $window_regs:ident, saved_bytecode_stack, move_field) => {
std::mem::take(&mut $recv.saved_bytecode_stack)
};
($recv:ident, $window_regs:ident, saved_immutables_stack, move_field) => {
std::mem::take(&mut $recv.saved_immutables_stack)
};
($recv:ident, $window_regs:ident, save_stack, move_field) => {
std::mem::take(&mut $recv.save_stack)
};
($recv:ident, $window_regs:ident, spill_stack, move_field) => {
std::mem::take(&mut $recv.spill_stack)
};
($recv:ident, $window_regs:ident, cell_stack, move_field) => {
std::mem::take(&mut $recv.cell_stack)
};
($recv:ident, $window_regs:ident, inline_callee, opt_copy) => {
$recv.inline_callee
};
($recv:ident, $window_regs:ident, active_upvalues, copy) => {
$recv.active_upvalues
};
($recv:ident, $window_regs:ident, generator_dispatch, flag_zero) => {{
let prev = $recv.generator_dispatch;
$recv.generator_dispatch = false;
prev
}};
($recv:ident, $window_regs:ident, async_dispatch, flag_zero) => {{
let prev = $recv.async_dispatch;
$recv.async_dispatch = false;
prev
}};
($recv:ident, $window_regs:ident, construct_dispatch, flag_zero) => {{
let prev = $recv.construct_dispatch;
$recv.construct_dispatch = false;
prev
}};
($recv:ident, $window_regs:ident, inline_strict, copy) => {
$recv.inline_strict
};
($recv:ident, $window_regs:ident, inline_frames_base, copy) => {
$recv.inline_frames_base
};
($recv:ident, $window_regs:ident, inline_args_base, copy) => {
$recv.inline_args_base
};
($recv:ident, $window_regs:ident, inline_args_count, copy) => {
$recv.inline_args_count
};
($recv:ident, $window_regs:ident, accessor_frame_target_reg, copy) => {
$recv.accessor_frame_target_reg
};
($recv:ident, $window_regs:ident, active_flat_id, copy) => {
$recv.active_flat_id
};
($recv:ident, $window_regs:ident, active_table_gen, copy) => {
$recv.active_table_gen
};
}
/// restore 方向的字段写回语句。`regs` 只回拷窗口并把缓冲归还池。
macro_rules! inline_restore_field {
($recv:ident, $saved:ident, regs, boxed_window) => {
$recv.regs[..$saved.regs.len()].copy_from_slice(&$saved.regs);
$recv.inline_reg_pool = Some(std::mem::take(&mut $saved.regs).into_vec());
};
($recv:ident, $saved:ident, saved_this, copy) => {
$recv.regs[254] = $saved.saved_this
};
($recv:ident, $saved:ident, saved_new_target, copy) => {
$recv.regs[255] = $saved.saved_new_target
};
($recv:ident, $saved:ident, pc, copy) => {
$recv.pc = $saved.pc
};
($recv:ident, $saved:ident, bytecode, move_field) => {
$recv.bytecode = std::mem::take(&mut $saved.bytecode)
};
($recv:ident, $saved:ident, active_immutables, copy) => {
$recv.active_immutables = $saved.active_immutables
};
($recv:ident, $saved:ident, active_reg_limit, copy) => {
$recv.active_reg_limit = $saved.active_reg_limit
};
($recv:ident, $saved:ident, root_reg_limit, copy) => {
$recv.root_reg_limit = $saved.root_reg_limit
};
($recv:ident, $saved:ident, try_stack, move_field) => {
$recv.try_stack = std::mem::take(&mut $saved.try_stack)
};
($recv:ident, $saved:ident, frames, frames_values) => {
$recv.frames = std::mem::take(&mut $saved.frames)
};
($recv:ident, $saved:ident, exception_value, opt_take) => {
$recv.exception_value = $saved.exception_value
};
($recv:ident, $saved:ident, pending_exception, opt_take) => {
$recv.pending_exception = $saved.pending_exception
};
($recv:ident, $saved:ident, pending_error_kind, opt_take) => {
$recv.pending_error_kind = $saved.pending_error_kind
};
($recv:ident, $saved:ident, pending_completion, opt_copy) => {
$recv.pending_completion = $saved.pending_completion
};
($recv:ident, $saved:ident, for_in_iters, for_in_keys) => {
// unwind 不弹 for-in 体:被调函数 for-in 异常逃出且未被接管时残留体留在
// 表内,快照回写是整向量替换。先逐条释放残留体再搬回快照,堆 Box 恰好
// 释放一次;for-of 栈随后由独立臂从快照还原,本臂顺带清位不影响终态。
$recv.iters.reset();
$recv.iters.for_in_iters = std::mem::take(&mut $saved.for_in_iters)
};
($recv:ident, $saved:ident, for_of_iters, iter_take) => {
$recv.iters.for_of_iters = std::mem::take(&mut $saved.for_of_iters)
};
($recv:ident, $saved:ident, saved_bytecode_stack, move_field) => {
$recv.saved_bytecode_stack = std::mem::take(&mut $saved.saved_bytecode_stack)
};
($recv:ident, $saved:ident, saved_immutables_stack, move_field) => {
$recv.saved_immutables_stack = std::mem::take(&mut $saved.saved_immutables_stack)
};
($recv:ident, $saved:ident, save_stack, move_field) => {
$recv.save_stack = std::mem::take(&mut $saved.save_stack)
};
($recv:ident, $saved:ident, spill_stack, move_field) => {
$recv.spill_stack = std::mem::take(&mut $saved.spill_stack)
};
($recv:ident, $saved:ident, cell_stack, move_field) => {
$recv.cell_stack = std::mem::take(&mut $saved.cell_stack)
};
($recv:ident, $saved:ident, inline_callee, opt_copy) => {
$recv.inline_callee = $saved.inline_callee
};
($recv:ident, $saved:ident, active_upvalues, copy) => {
$recv.active_upvalues = $saved.active_upvalues
};
($recv:ident, $saved:ident, generator_dispatch, flag_zero) => {
$recv.generator_dispatch = $saved.generator_dispatch
};
($recv:ident, $saved:ident, async_dispatch, flag_zero) => {
$recv.async_dispatch = $saved.async_dispatch
};
($recv:ident, $saved:ident, construct_dispatch, flag_zero) => {
$recv.construct_dispatch = $saved.construct_dispatch
};
($recv:ident, $saved:ident, inline_strict, copy) => {
$recv.inline_strict = $saved.inline_strict
};
($recv:ident, $saved:ident, inline_frames_base, copy) => {
$recv.inline_frames_base = $saved.inline_frames_base
};
($recv:ident, $saved:ident, inline_args_base, copy) => {
$recv.inline_args_base = $saved.inline_args_base
};
($recv:ident, $saved:ident, inline_args_count, copy) => {
$recv.inline_args_count = $saved.inline_args_count
};
($recv:ident, $saved:ident, accessor_frame_target_reg, copy) => {
$recv.accessor_frame_target_reg = $saved.accessor_frame_target_reg
};
($recv:ident, $saved:ident, active_flat_id, copy) => {
$recv.active_flat_id = $saved.active_flat_id
};
($recv:ident, $saved:ident, active_table_gen, copy) => {
$recv.active_table_gen = $saved.active_table_gen
};
}
/// 内联同步调用可搬移执行核心字段的单一登记表。save/restore 双向由本宏展开;
/// 新增字段只加一行(字段名, 操作符)。注释 = 该字段语义(M 搬移 / V 含 JsValue)。
/// `$window_regs` 只在 save 方向被 `regs` 字段消费。
macro_rules! inline_core_fields {
($recv:ident, $saved:ident, $ops:ident, $window_regs:ident) => {
$ops!(
$recv,
$saved,
$window_regs,
(regs, boxed_window), // V M
(saved_this, copy), // V
(saved_new_target, copy), // V
(pc, copy), // M
(bytecode, move_field), // V M
(active_immutables, copy), // M
(active_reg_limit, copy), // M
(root_reg_limit, copy), // M
(try_stack, move_field), // M
(frames, frames_values), // V M
(exception_value, opt_take), // V M
(pending_exception, opt_take), // V M
(pending_error_kind, opt_take), // M
(pending_completion, opt_copy), // V M
(for_in_iters, for_in_keys), // V M
(for_of_iters, iter_take), // V M
(saved_bytecode_stack, move_field), // M
(saved_immutables_stack, move_field), // M
(save_stack, move_field), // V M
(spill_stack, move_field), // V M
(cell_stack, move_field), // V M
(inline_callee, opt_copy), // V M
(active_upvalues, copy), // M
(generator_dispatch, flag_zero), // M 调度标志:快照属主值,嵌套期间清零
(async_dispatch, flag_zero), // M 调度标志:快照属主值,嵌套期间清零
(construct_dispatch, flag_zero), // M 调度标志:快照属主值,嵌套期间清零
(inline_strict, copy), // M
(inline_frames_base, copy), // M
(inline_args_base, copy), // M
(inline_args_count, copy), // M
(accessor_frame_target_reg, copy), // M
(active_flat_id, copy), // M
(active_table_gen, copy), // M
)
};
}
/// 把字段表展开为 `InlineSyncState` 结构体字面量(字段名在 ident 位置,值由
/// `inline_save_field` 产出)。
macro_rules! inline_save {
($recv:ident, $saved:ident, $window_regs:ident, $(($field:ident, $op:ident)),* $(,)?) => {
InlineSyncState {
$($field: inline_save_field!($recv, $window_regs, $field, $op)),*
}
};
}
/// 把字段表展开为写回语句序列。
macro_rules! inline_restore {
($recv:ident, $saved:ident, $window_regs:ident, $(($field:ident, $op:ident)),* $(,)?) => {
{ $(inline_restore_field!($recv, $saved, $field, $op);)* }
};
}
impl Vm {
/// 保存当前 VM 执行状态到堆上(内联同步调用与生成器恢复共用)。
///
/// 寄存器只保存窗口 `regs[0..min(regs_end, 254)]` 的副本,`regs[254]/[255]`
/// 单独存入 `saved_this`/`saved_new_target`。窗口缓冲取自 `inline_reg_pool`
/// 复用,热回调循环内零分配;嵌套时池为空则新分配。
///
/// # 边界与前提
/// - `regs_end` ≤ 254 表示窗口化保存;传 256(全量)等价于保存全部寄存器
/// (254 个通用槽 0..=253 + 254/255 单独存)。
/// - 调用方保证 `regs_end ≤ 256`;窗口上限 254 覆盖 callee 写入集
/// (`n_registers ≤ 254`,含 RegAlloc 最高合法物理槽 253)。
pub(crate) fn save_inline_state(&mut self, regs_end: usize) -> Box<InlineSyncState> {
vm_trace!("save_inline_state: pc={} depth={}", self.pc, self.frames.len());
// 固化不变量:内联 state-swap 边界不携带在途异步逃出(挂起态快照前已搬入
// 状态盒,settle 前恒为 None)。
debug_assert!(self.pending_async_escape.is_none());
let window = regs_end.min(254);
let mut window_regs = self.inline_reg_pool.take().unwrap_or_default();
window_regs.clear();
window_regs.extend_from_slice(&self.regs[..window]);
let vm = self;
Box::new(inline_core_fields!(vm, vm, inline_save, window_regs))
}
/// 把 [`save_inline_state`] 保存的状态恢复回 VM。窗口回拷 + `regs[254]/[255]`
/// 单回,窗口外寄存器 callee 未触碰无需恢复。
pub(crate) fn restore_inline_state(&mut self, mut saved: Box<InlineSyncState>) {
vm_trace!("restore_inline_state: pc={}", saved.pc);
// 固化不变量:内联 state-swap 边界不携带在途异步逃出(与 save 侧同)。
debug_assert!(self.pending_async_escape.is_none());
let vm = self;
let _window_regs: Vec<JsValue> = Vec::new();
inline_core_fields!(vm, saved, inline_restore, _window_regs);
// 窗口回拷已复活调用方陈旧镜像槽:重载调用方模块的 builtin 名集
// (active_flat_id 已随宏回写还原到调用方);外层 native pack 实参区
// 在飞时跳过该域,实参值由窗口拷回还原、不在此刷新。
vm.reload_active_module_mirror_slots(vm.native_pack_end);
}
/// 首次执行的 VM 就绪:清空执行核心(regs/pc/bytecode/各栈段/迭代器/内联态),
/// 压入首帧。参数初始化步与 async/gen 调度标志由调用方管理。
///
/// # 边界
/// callee 非函数 / sub_idx 越界 / 超调用深度时由 push_bytecode_frame 返回 Err。
pub(crate) fn prepare_execution_initial(
&mut self, callee: JsValue, this_value: JsValue, args: &[JsValue],
) -> Result<(), String> {
self.regs = [JsValue::undefined(); 256];
self.pc = 0;
self.bytecode = Arc::default();
self.active_reg_limit = 1;
self.root_reg_limit = 1;
self.try_stack.clear();
self.iters.reset();
self.spill_stack.clear();
self.save_stack.clear();
self.saved_bytecode_stack.clear();
self.saved_immutables_stack.clear();
self.cell_stack.clear();
self.inline_callee = None;
self.push_bytecode_frame(
callee,
this_value,
FrameArgs::Slice(args),
None,
None,
JsValue::undefined(),
FrameContinuation::None,
0,
)
}
/// 内联同步调用字节码函数:不起新 CallFrame,直接在调用方寄存器窗口内执行被调模块。
///
/// 平表按被调对象创建期 `table_gen` 解析(跨 run 调用仍命中定义模块所在原表);
/// 生成器与异步生成器函数不执行函数体,分别返回迭代器/异步迭代器对象;异步函数返回
/// capability promise 并立即同步执行函数体到首个 await。
/// 普通函数经 `save_inline_state` 保存窗口后在调用方寄存器文件上执行子模块,结束
/// 时 `restore_inline_state` 回拷窗口。
///
/// # 步骤
/// 1. `sub_module_index == 0` 为 native 哨兵(accessor 不可调用),直接返回 TypeError。
/// 2. 按创建期代际取平表并校验下标;调用深度达上限抛 RangeError。
/// 3. 生成器/异步/异步生成器分流到对应对象构造,不进入 dispatch。
/// 4. 保存窗口与 pc,零填 callee 读区,装载字节码/常量/平表 id,实参写入 spill 栈实参区。
/// 5. 绑定 `regs[254]`:箭头函数用创建期捕获的 `this`,sloppy 普通函数在 nullish
/// receiver 上替换全局对象,严格模式与显式 receiver 原样保留;`regs[255]` 置 undefined。
/// 6. 执行 dispatch 后恢复调用方状态并返回结果。
///
/// # 边界与前提
/// - 表代际未注册或 `sub_module_index` 越界返回 `Err`,不 panic。
/// - 实参超出 `n_args` 的部分忽略;缺位参数填 undefined。
///
/// # 副作用
/// - 改写全局寄存器文件、pc、字节码/常量/平表 id、spill 栈与 `cell_stack`;
/// 结束前经 `restore_inline_state` 全部还原。
pub(crate) fn call_bytecode_function_inline(
&mut self, callee: JsValue, callee_obj: &JsObject, receiver: JsValue, args: &[JsValue],
) -> Result<JsValue, String> {
if callee_obj.sub_module_index() == 0 {
return Err(self.error_message_text("TypeError", "accessor is not callable"));
}
let sub_idx = callee_obj.sub_module_index() as usize;
let gen = callee_obj.table_gen();
vm_debug!(
"call_bytecode_function_inline: sub_idx={}, args={}, depth={}",
sub_idx,
args.len(),
self.frames.len()
);
let table = match self.tables.get(&gen) {
Some(t) => t,
None => return Err(format!("accessor: module table gen {} not available", gen)),
};
if sub_idx >= table.modules.len() {
return Err(format!(
"accessor sub_module_index {} out of bounds (max {})",
sub_idx,
table.modules.len()
));
}
if self.frames.len() >= self.kernel_core.config.max_call_depth {
self.raise_error_kind("RangeError", "Maximum call stack size exceeded")?;
return Ok(JsValue::undefined());
}
if self.native_call_depth >= self.kernel_core.config.max_call_depth {
self.raise_error_kind("RangeError", "Maximum call stack size exceeded")?;
return Ok(JsValue::undefined());
}
// 被调模块取 Arc 克隆(与调用方共享同一编译产物):后续 &mut self
// 调用期间不持有对注册表的借用。
let sub = Arc::clone(&table.modules[sub_idx]);
// 异步生成器函数调用返回异步生成器迭代器对象,next/return/throw 返回 Promise。
if sub.is_generator && sub.is_async {
return self.create_async_generator_object(callee, receiver, args);
}
// 生成器函数调用返回迭代器对象,不执行函数体。
if sub.is_generator {
return self.create_generator_object(callee, receiver, args);
}
// 异步函数调用返回 capability promise,立即同步执行 body 到首个 await。
if sub.is_async {
return self.create_async_object(callee, receiver, args);
}
self.native_call_depth += 1;
vm_trace!("call_bytecode: saving state pc={} depth={}", self.pc, self.frames.len());
// 窗口 = max(调用方活跃寄存器, callee 寄存器数):restore 只回拷窗口,正好
// 覆盖 callee 写入区(regs[0..n_registers] + 254/255)与调用方活动区。
let window = self.active_reg_limit.max(sub.n_registers).max(1) as usize;
let saved = self.save_inline_state(window);
// 只零填 callee 读区 regs[0..n_registers];高位残留调用方旧值无害
// (callee 字节码只访问自身 n_registers 内)。
for r in 0..sub.n_registers as usize {
self.regs[r] = JsValue::undefined();
}
self.pc = 0;
self.bytecode = Arc::clone(&sub.bytecode);
self.activate_immutables(gen, sub_idx, &sub.constants);
self.active_flat_id = sub_idx as u32;
self.active_table_gen = gen;
self.active_reg_limit = sub.n_registers.max(1);
self.root_reg_limit = self.active_reg_limit;
self.cell_stack.push(Vec::with_capacity(sub.cells_needed as usize));
self.inline_callee = Some(callee);
// 内联无帧:活动镜像直接取被调对象表(非闭包为 null 空切片)。
self.active_upvalues = callee_obj.upvalues_slice() as *const [*mut Cell];
// inline 无 CallFrame:目标函数严格模式单独记录,内联执行期间的写路径
// strict/sloppy 判定据此分派(嵌套内联时外层值由 InlineSyncState 恢复)。
self.inline_strict = sub.is_strict;
// 帧基线快照:内联期间新压的帧(CALL/accessor)越过基线,其严格性归帧栈顶。
self.inline_frames_base = self.frames.len();
// inline 同步调用无 CallFrame:完整实参写入 spill 栈实参区,供 CREATE_ARGUMENTS 读取。
self.inline_args_base = self.spill_stack.len() as u32;
self.spill_stack.extend_from_slice(args);
self.inline_args_count = args.len().min(u16::MAX as usize) as u16;
for i in 0..sub.n_args as usize {
self.regs[sub.param_base as usize + i] = args.get(i).copied().unwrap_or(JsValue::undefined());
}
// this 绑定:箭头函数恒用词法捕获;sloppy 普通函数 this 为 null/undefined 时
// 替换为全局对象,其余原始值经 ToObject 盒装(ECMA-262 10.4.3);严格模式
// this 原样保留。
self.regs[254] = if sub.is_arrow {
callee_obj.captured_this()
} else if !sub.is_strict && receiver.is_nullish() {
JsValue::from_js_object(self.realm.session.borrow().global_object().as_ptr() as *mut JsObject)
} else if sub.is_strict {
receiver
} else {
oxide_runtime_api::to_object(receiver, self)?
};
self.regs[255] = JsValue::undefined();
self.reload_builtin_mirror_slots(&sub.builtin_reg_map, 0);
let _ = callee;
vm_trace!(
"call_bytecode: dispatching sub_idx={} n_regs={} n_args={} arrow={}",
sub_idx,
sub.n_registers,
sub.n_args,
sub.is_arrow
);
let result = self.dispatch();
self.native_call_depth -= 1;
vm_trace!("call_bytecode: restoring state pc={} result={:?}", saved.pc, result.as_ref().ok());
self.restore_inline_state(saved);
result
}
/// 从调用帧恢复调用方执行状态:弹回字节码/平表 id/表代际/常量四组保存栈,
/// 回拷调用方寄存器窗口并按 `return_addr` 回写 pc。
///
/// 帧恢复只负责执行核心状态;被调函数的返回值传递由调用路径另行处理。
///
/// # 边界与前提
/// - 保存栈为空时对应字段保持原值(不 panic);`saved_reg_offset` 与
/// `caller_reg_limit` 由压帧时按调用方活动上界写入,须与其匹配。
///
/// # 副作用
/// - 改写 pc、字节码、活动平表 id/代际、活动常量、寄存器窗口与 `regs[254]/[255]`、
/// save_stack/spill_stack、`active_reg_limit`,并重载调用方 builtin 镜像槽。
pub(crate) fn restore_frame(&mut self, frame: CallFrame) {
vm_trace!(
"restore_frame: return_addr={} caller_reg_limit={}",
frame.return_addr,
frame.caller_reg_limit
);
// 调用返回:sloppy 函数在 dispatch_create_arguments 把 own arguments 属性
// 设为本调用的 arguments 对象;返回后须清空(置 undefined),否则强引用
// 保活最后一次调用的 arguments 对象,跨 epoch 晋升后泄漏。仅当属性在场时
// 清空(strict 函数无 own arguments,避免误建)。
if !frame.strict && frame.callee.is_object() {
let arguments_si = self.kernel_core.perm_interner().intern("arguments").0;
let callee_ref = unsafe { &*frame.callee.as_js_object_ptr() };
if let Some(slot) = self.get_own_property_slot(callee_ref, arguments_si) {
let callee_mut = unsafe { &mut *frame.callee.as_js_object_ptr() };
callee_mut.set_prop_at(slot, JsValue::undefined());
}
}
// 弹帧后活动镜像切回新栈顶的表(帧空即顶层空切片)。
self.active_upvalues = self
.frames
.last()
.map(|f| f.upvalues)
.unwrap_or(std::ptr::slice_from_raw_parts(std::ptr::null(), 0));
if let Some(saved_bc) = self.saved_bytecode_stack.pop() {
self.bytecode = saved_bc;
}
if let Some(saved_flat) = self.saved_flat_id_stack.pop() {
self.active_flat_id = saved_flat;
}
if let Some(saved_gen) = self.saved_table_gen_stack.pop() {
self.active_table_gen = saved_gen;
}
vm_debug!(
"restore_frame: return_addr={} bc_len={} saved_stack={} fn={:?}",
frame.return_addr,
self.bytecode.len(),
self.saved_bytecode_stack.len(),
self.kernel_core
.perm_interner()
.lookup(frame.function_name)
.map(|s| s.to_string())
);
if let Some(saved_imm) = self.saved_immutables_stack.pop() {
self.active_immutables = saved_imm;
}
let offset = frame.saved_reg_offset as usize;
let len = frame.caller_reg_limit as usize;
self.regs[..len].copy_from_slice(&self.save_stack[offset..offset + len]);
self.save_stack.truncate(offset);
// spill 帧边界截断:子函数写入的 spill 数据在帧恢复后丢弃。
self.spill_stack.truncate(frame.spill_offset as usize);
self.regs[254] = frame.saved_this;
self.regs[255] = frame.saved_new_target;
// active_reg_limit 还原为调用方真实值(caller_reg_limit 可能被存活上界截断)。
self.active_reg_limit = frame.caller_active_reg_limit;
self.pc = frame.return_addr;
// 窗口回拷已复活调用方陈旧镜像槽:重载调用方模块的 builtin 名集
// (active_flat_id 已随保存栈弹回还原到调用方);外层 native pack
// 实参区在飞时跳过该域,实参值由窗口拷回还原、不在此刷新。
self.reload_active_module_mirror_slots(self.native_pack_end);
}
/// 重新执行当前已加载的 bytecode:清空执行状态并重置 IC 缓存后再次 dispatch。
///
/// # 注意事项
/// - 清空寄存器文件后按 run 初始状态恢复顶层 this(regs[254]):不恢复则
/// 重执行中依赖 this 的顶层写(顶层 var 全局同步写)在 undefined 上
/// 静默 no-op,循环计数不推进。
pub fn rerun(&mut self) -> Result<JsValue, String> {
vm_info!("rerun: clearing IC caches");
self.clear_execution_state();
self.active_reg_limit = self.root_reg_limit;
self.regs[254] = self.top_level_this;
// 寄存器文件清空后镜像槽须重载(与 run 入口同语义),否则裸读回陈旧值。
self.reload_active_module_mirror_slots(0);
crate::ic_helper::clear_ic_caches(self.bytecode_mut());
self.dispatch()
}
/// run 边界回收无存活函数对象引用的子模块表代际:扫 session 对象表收
/// 存活函数对象引用的表代际集,注册表中其余代际 drop。表内存由此有界于
/// 存活函数对象数,不随 run 数单调增;存活函数按创建期代际仍命中原表。
///
/// # 注意事项
/// - 须在换表注册新代际**之前**调用(此刻 current_gen 仍指上一 run 的表,
/// 新表未注册天然不被回收)。
/// - 执行外安全点调用(dispatch 未重入、无在途 builtin 局部裸指针),
/// session 对象表此刻全部有效。
pub(crate) fn reclaim_unreferenced_tables(&mut self) {
// 存活代际集:函数对象只经创建点直落 session 对象表登记(或晋升克隆
// 随行),扫全表即完备;native 函数哨兵 sub_module_index == 0 不计数。
let mut live_gens: Vec<u32> = Vec::new();
for &ptr in &self.realm.gc.borrow().session_object_ptrs {
if ptr.is_null() {
continue;
}
// SAFETY: session 对象表此刻未清空,指针指向 session arena 内合法对象。
let obj = unsafe { &*ptr };
if obj.is_function() && obj.sub_module_index() != 0 {
live_gens.push(obj.table_gen());
}
}
// 当前代际恒保留(本 run 执行仍在用,即便尚无函数引用)。
live_gens.push(self.current_gen);
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 缓存条目):平表装载只做
/// Arc::clone,run 期零模块拷贝。内部初始化寄存器/bytecode/immutables 与
/// builtin 寄存器预绑定,然后进入 dispatch 主循环;执行完成或异常展开后返回。
pub fn run(&mut self, module: &Arc<CompiledModule>) -> Result<JsValue, String> {
vm_debug!("run: starting bytecode execution, {} instructions", module.bytecode.len());
self.clear_execution_state();
// 模板对象缓存按 run 清空:缓存只留本次 run 的条目,规模有界(键含代际
// 维度后跨 run 不再误命中,清空仅为规模约束)。
self.template_objects.clear();
self.saved_flat_id_stack.clear();
self.saved_table_gen_stack.clear();
self.active_flat_id = 0;
self.cell_stack.clear();
self.cell_stack.push(Vec::new());
// run 边界换表前先回收无存活函数对象引用的表代际(此刻 current_gen 仍
// 指上一 run 的表,存活代际集由 session 对象表扫描得出)。
self.active_immutables = std::ptr::slice_from_raw_parts(std::ptr::null(), 0);
self.reclaim_unreferenced_tables();
// 新表注册于 current_gen + 1:本 run 创建的函数对象记录该代际,跨 run
// 调用按创建期代际解析。
self.current_gen = self.current_gen.wrapping_add(1);
let modules = Arc::new(collect_flat_modules(module));
self.tables.insert(
self.current_gen,
Box::new(TableGen {
immutables: (0..modules.len()).map(|_| OnceLock::new()).collect(),
si_tables: (0..modules.len()).map(|_| OnceLock::new()).collect(),
modules,
}),
);
self.active_table_gen = self.current_gen;
// 顶层脚本严格模式:无帧且无 inline 时写路径的 strict/sloppy 判定来源。
self.top_level_strict = module.is_strict;
self.bytecode = Arc::clone(&module.bytecode);
self.activate_immutables(self.current_gen, 0, &module.constants);
self.root_reg_limit = module.n_registers.max(1);
self.active_reg_limit = self.root_reg_limit;
self.reload_builtin_mirror_slots(&module.builtin_reg_map, 0);
// 顶层 this:脚本为全局对象(ECMA-262 全局执行上下文);
// ES module 顶层环境 GetThisBinding 返回 undefined。记录到
// top_level_this:rerun 清空寄存器文件后按此恢复顶层 this。
let global_ptr = self.realm.session.borrow().global_object().as_ptr() as *mut JsObject;
let this_val = if module.is_es_module {
JsValue::undefined()
} else {
JsValue::from_js_object(global_ptr)
};
self.top_level_this = this_val;
self.regs[254] = this_val;
let result = self.execute_task(|vm| vm.dispatch());
// 顶层执行结束后 drain 微任务队列:Promise reactions 与 thenable 委托在此执行。
self.drain_microtasks();
// 指令周期采样:run 末聚合本 run 的样本并输出 top-K 直方图(关闭时零开销
// 短路)。放 dispatch 返回后而非循环内:嵌套 dispatch 会多次返回,直方图
// 只应输出一次。
self.print_sample_histogram();
result
}
/// 按 (flat_id, 表代际) 聚合采样记录并向 stderr 输出 top-K 热点直方图。
///
/// 聚合键取 (flat_id, 表代际, pc):样本数按 (flat_id, 表代际) 汇总,top pc
/// 取该键下样本数最多的字节码偏移(与反汇编 offset 列同单位)。函数名经样本
/// 记录表代际的平表解析(顶层为 0),未知 flat_id 输出 `<flat N>`。
///
/// # 边界
/// - 采样关闭(`period == 0`)或无样本时直接返回,零输出零分配。
/// - 只读采样记录与平表,不改写任何执行状态。
pub(crate) fn print_sample_histogram(&self) {
if self.sampling.period == 0 || self.sampling.records.is_empty() {
return;
}
// 聚合:(flat_id, 表代际, pc) → 样本数与 opcode;
// (flat_id, 表代际) → 最大 frames 深度。
let mut pc_hist: HashMap<(u32, u32, u32), (u64, u8)> = HashMap::new();
let mut flat_max_frames: HashMap<(u32, u32), u32> = HashMap::new();
for &(flat, pc, op, frames, gen) in &self.sampling.records {
let entry = pc_hist.entry((flat, gen, pc)).or_insert((0, op));
entry.0 += 1;
let top = flat_max_frames.entry((flat, gen)).or_insert(frames);
if frames > *top {
*top = frames;
}
}
// 按 (flat_id, 表代际) 汇总样本数,降序排列后取 top-K。
let mut flat_total: HashMap<(u32, u32), u64> = HashMap::new();
for (&(flat, gen, _), &(count, _)) in &pc_hist {
*flat_total.entry((flat, gen)).or_insert(0) += count;
}
let mut ranked: Vec<((u32, u32), u64)> = flat_total.into_iter().collect();
ranked.sort_by(|a, b| b.1.cmp(&a.1).then(a.0.cmp(&b.0)));
let k = self.sampling.top_k.min(ranked.len());
eprintln!(
"sample top {k} (period {}, {} samples, {} instructions):",
self.sampling.period,
self.sampling.records.len(),
self.profiling.instruction_count
);
for (rank, &((flat, gen), count)) in ranked.iter().take(k).enumerate() {
// top pc:该 (flat_id, 表代际) 下样本数最多的字节码偏移(并列取哈希
// 遍历首个,非确定,诊断用途可接受)。
let mut top_pc: Option<(u64, u32, u8)> = None;
for (&(f, g, pc), &(c, op)) in &pc_hist {
if f != flat || g != gen {
continue;
}
match top_pc {
None => top_pc = Some((c, pc, op)),
Some((tc, _, _)) if c > tc => top_pc = Some((c, pc, op)),
Some(_) => {}
}
}
let (top_count, top_pc, top_op) = top_pc.unwrap_or((0, 0, 0));
let op_name = OpCode::try_from(top_op)
.map(|o| o.to_string())
.unwrap_or_else(|_| format!("op{top_op}"));
let name = self
.tables
.get(&gen)
.and_then(|t| t.modules.get(flat as usize))
.and_then(|m| m.function_name.clone())
.unwrap_or_else(|| format!("<flat {flat}>"));
let frames = flat_max_frames.get(&(flat, gen)).copied().unwrap_or(0);
eprintln!(
" {rank:>2}. {name:<24} (flat {flat}) {count:>8} samples top pc={top_pc} {op_name} ({top_count}) frames={frames}"
);
}
}
/// 异常展开:沿 `try_stack` 查找最近可接管的 catch/finally,无则逐帧回退并返回未捕获错误。
///
/// 与 `TRY_FINALLY_END` 的完成穿越逻辑对应:finally 体在途异常经 `pending_exception`
/// 悬挂,`finally_active` 标记其已进入,防止同一 finally 重复执行;处理器作用域内被
/// 中断的 for-of 循环先经 `close_for_of_above` 执行 IteratorClose。
///
/// # 步骤
/// 1. 逐 handler 展开:所属帧已返回(`frame_depth` 超出当前帧数)的残留 handler 跳过。
/// 2. 弹帧至 handler 深度,逐帧弹 `cell_stack` 并 `restore_frame` 回写调用方状态。
/// 3. 关闭该作用域内被中断的 for-of 迭代器。
/// 4. 有 finally:未进入则挂起在途异常并转入 finally 体;已进入(finally 体内新异常)
/// 则丢弃在途异常继续向外展开,不重入本 finally。
/// 5. 有 catch:在途异常写入 `regs[0]`,pc 转向 catch。
/// 6. 无可用处理器:关闭全部迭代器、回退所有帧,记 `last_uncaught_value` 并返回 `Err`。
///
/// # 边界与前提
/// - `exception_value` 为空时按 undefined 处理;`try_stack` 为空直接走未捕获路径。
///
/// # 副作用
/// - 弹出 `try_stack`/`frames`/`cell_stack`,改写 pc 与寄存器窗口;未捕获时把逃逸
/// 异常值存入 `last_uncaught_value`(for-of 的 next() 抛出经此重抛原值而非展平
/// 字符串),并消费 `pending_error_kind`。
pub(crate) fn unwind(&mut self) -> Result<(), String> {
vm_debug!("unwind: {} try handlers on stack", self.try_stack.len());
while let Some(mut handler) = self.try_stack.pop() {
if handler.frame_depth > self.frames.len() {
// 残留 handler 所属帧已返回(return 逃出泄漏的兜底):丢弃防跳回
// 死函数的 catch/finally——否则 catch 再抛会反复弹出同一 handler
// 形成死循环。其 for_of_depth 快照属于已死帧,也不可据此关闭
// 当前帧的迭代器,故整体跳过。
continue;
}
while self.frames.len() > handler.frame_depth {
if let Some(frame) = self.frames.pop() {
self.cell_stack.pop();
self.restore_frame(frame);
}
}
// 对该处理器作用域内被中断的 for-of 循环执行 IteratorClose。
self.close_for_of_above(handler.for_of_depth);
if let Some(finally_pc) = handler.finally_pc {
if handler.finally_active {
// 新异常在 finally 体内抛出:覆盖在途异常与完成,继续向外展开,
// 不再重入本 finally(否则已执行的 finally 会重复运行)。
self.pending_exception = None;
self.pending_error_kind = None;
self.pending_completion = None;
continue;
}
vm_trace!("unwind: entering finally at pc={}", finally_pc);
self.pending_exception = Some(self.exception_value.take().unwrap_or(JsValue::undefined()));
handler.finally_active = true;
self.try_stack.push(handler);
self.pc = finally_pc;
// 在途异常已进入 finally 处理流程:槽作废,防残留值被后续 take 误取。
self.last_uncaught_value = None;
return Ok(());
}
if let Some(catch_pc) = handler.catch_pc {
vm_trace!("unwind: caught at pc={}", catch_pc);
let exc = self.exception_value.take().unwrap_or(JsValue::undefined());
self.regs[0] = exc;
self.pc = catch_pc;
// 在途异常已写入 catch 参数:槽作废,防残留值被后续 take 误取。
self.last_uncaught_value = None;
return Ok(());
}
}
self.close_for_of_above(0);
while let Some(frame) = self.frames.pop() {
self.cell_stack.pop();
self.restore_frame(frame);
}
let exc = self.exception_value.take().unwrap_or(JsValue::undefined());
// 保留逃逸的 JsValue,使 for-of 的 next() 抛出时可重新抛出原值而非展平后的
// 字符串(由 dispatch_for_of_done/next 消费)。
self.last_uncaught_value = Some(exc);
let kind_str = self.pending_error_kind.take().unwrap_or("Error");
let exc_text = self.error_text(exc);
let msg = if exc_text.starts_with(kind_str) {
format!("uncaught {exc_text}")
} else {
format!("uncaught {kind_str}: {exc_text}")
};
vm_warn!("unwind: uncaught {}: {}", kind_str, exc_text);
Err(msg)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::vm::Completion;
use oxide_types::value::JsValue;
fn compile(source: &str) -> oxide_bytecode::module::CompiledModule {
let allocator = oxide_parser::Allocator::default();
let program = oxide_parser::parse(&allocator, source).expect("parse");
oxide_compiler::compiler::Compiler::new().compile(&program).expect("compile")
}
#[test]
fn top_level_module_entry_shares_host_arc() {
// 顶层平表条目 = 宿主模块 Arc 同一实例:run 期零模块克隆,
// 克隆会产出不同 Arc,本钉在此情形必失败。
let mut vm = Vm::new();
let module = Arc::new(compile("var o = { a: 1 }; o.a + o.a"));
vm.run(&module).expect("run1");
assert!(Arc::ptr_eq(&vm.current_table().modules[0], &module), "顶层条目须与宿主 Arc 同一实例");
vm.full_reset();
vm.run(&module).expect("run2");
assert!(Arc::ptr_eq(&vm.current_table().modules[0], &module), "full_reset 后二次 run 仍共享");
}
#[test]
fn top_level_run_keeps_host_bytecode_ic_words_zero() {
// 宿主侧 bytecode 的 IC 扩展字在 run 后恒零:IC 写回只发生在
// Vm.bytecode 的 COW 私有拷贝上——dispatch 期平表与 Vm.bytecode 双持
// 共享缓冲(refcount ≥ 2),make_mut 必先深拷贝再写,宿主共享缓冲
// 结构上不可写。
let mut vm = Vm::new();
let module = Arc::new(compile("var o = { a: 1 }; o.a + o.a"));
vm.run(&module).expect("run");
assert!(vm.ic_miss_count() > 0, "源含属性读取,应发生 IC miss 与写回");
// 以 clear_ic_caches 的扫描口径作 oracle:IC 扩展字若已全零,清零是 no-op。
let original: Vec<oxide_bytecode::opcode::Instr> = module.bytecode.to_vec();
let mut probe = original.clone();
crate::ic_helper::clear_ic_caches(&mut probe);
assert_eq!(probe, original, "宿主侧 IC 扩展字须全零");
// 写回未落宿主缓冲:run 末 Vm.bytecode 是 COW 私有拷贝(不同分配),
// 而非宿主共享缓冲本身。
assert!(
!Arc::ptr_eq(&module.bytecode, &vm.bytecode),
"Vm.bytecode 须为 COW 私有拷贝,写回不得落宿主缓冲"
);
assert_eq!(vm.bytecode.len(), module.bytecode.len());
}
#[test]
fn save_restore_inline_round_trip() {
// 构造带哨兵的 VM → save → 恢复 → 断言全部字段逐字往返(含 accessor_frame_target_reg)。
let mut vm = Vm::new();
vm.regs[3] = JsValue::float(1.0);
vm.regs[254] = JsValue::float(254.0);
vm.regs[255] = JsValue::float(255.0);
vm.pc = 7;
vm.active_immutables = std::ptr::slice_from_raw_parts(std::ptr::null(), 0);
vm.active_reg_limit = 4;
vm.root_reg_limit = 5;
vm.exception_value = Some(JsValue::float(6.0));
vm.pending_exception = Some(JsValue::float(7.0));
vm.pending_error_kind = Some("TypeError");
vm.pending_completion = Some(Completion::Return {
value: JsValue::float(8.0),
remaining_finally: 1,
for_of_count: 0,
for_in_count: 0,
});
vm.iters.for_of_iters.push(crate::vm_state::ForOfEntry {
iterator: JsValue::float(8.5),
last_result: JsValue::float(9.0),
is_async: false,
fast: None,
fast_value: JsValue::undefined(),
fast_inner: JsValue::undefined(),
fast_cursor: 0,
});
vm.spill_stack.push(JsValue::float(10.0));
vm.save_stack.push(JsValue::float(11.0));
vm.inline_callee = Some(JsValue::float(12.0));
vm.inline_args_base = 2;
vm.inline_args_count = 3;
vm.accessor_frame_target_reg = Some(9);
let saved = vm.save_inline_state(256);
vm.regs[3] = JsValue::float(99.0);
vm.accessor_frame_target_reg = None;
vm.restore_inline_state(saved);
assert_eq!(vm.regs[3], JsValue::float(1.0));
assert_eq!(vm.regs[254], JsValue::float(254.0));
assert_eq!(vm.regs[255], JsValue::float(255.0));
assert_eq!(vm.pc, 7);
assert_eq!(vm.active_reg_limit, 4);
assert_eq!(vm.root_reg_limit, 5);
assert_eq!(vm.exception_value, Some(JsValue::float(6.0)));
assert_eq!(vm.pending_exception, Some(JsValue::float(7.0)));
assert_eq!(vm.pending_error_kind, Some("TypeError"));
assert!(matches!(
vm.pending_completion,
Some(Completion::Return { value, remaining_finally: 1, .. }) if value == JsValue::float(8.0)
));
assert_eq!(vm.iters.for_of_iters.len(), 1);
assert_eq!(vm.iters.for_of_iters[0].iterator, JsValue::float(8.5));
assert_eq!(vm.iters.for_of_iters[0].last_result, JsValue::float(9.0));
assert_eq!(vm.spill_stack, vec![JsValue::float(10.0)]);
assert_eq!(vm.save_stack, vec![JsValue::float(11.0)]);
assert_eq!(vm.inline_callee, Some(JsValue::float(12.0)));
assert_eq!(vm.inline_args_base, 2);
assert_eq!(vm.inline_args_count, 3);
assert_eq!(vm.accessor_frame_target_reg, Some(9));
}
#[test]
fn save_restore_inline_window_254_preserves_r253() {
// 窗口化路径边界:调用方 active_reg_limit = 254 时窗口覆盖物理槽 253
// (RegAlloc 最高合法色),save/restore 必须往返 regs[253],且与 254/255
// 单存槽位互不重叠。
let mut vm = Vm::new();
vm.regs[253] = JsValue::float(253.0);
vm.regs[254] = JsValue::float(254.0);
vm.regs[255] = JsValue::float(255.0);
let saved = vm.save_inline_state(254);
vm.regs[253] = JsValue::float(99.0);
vm.regs[254] = JsValue::float(98.0);
vm.regs[255] = JsValue::float(97.0);
vm.restore_inline_state(saved);
assert_eq!(vm.regs[253], JsValue::float(253.0));
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());
}
}