mirror of
https://github.com/pjh456/OxideJS.git
synced 2026-10-24 02:31:13 +08:00
642 lines
35 KiB
Rust
642 lines
35 KiB
Rust
//! 基于寄存器的 JS 虚拟机:执行状态、寄存器文件、调用栈与 session 内存,
|
||
//! 以及同步调用、指令主循环、异常通道、GC 钩子、属性解析等子模块的装配。
|
||
|
||
#![allow(clippy::arc_with_non_send_sync)]
|
||
|
||
use std::collections::{HashMap, VecDeque};
|
||
use std::path::PathBuf;
|
||
use std::sync::Arc;
|
||
#[cfg(test)]
|
||
use std::sync::OnceLock;
|
||
|
||
#[cfg(test)]
|
||
use oxide_bytecode::module::CompiledModule;
|
||
use oxide_bytecode::opcode;
|
||
use smallvec::SmallVec;
|
||
|
||
/// 初始化一个 session 的内置对象(global 槽位、各构造器与原型、IC 预热)。
|
||
pub use crate::bindings::init_kernel_builtins;
|
||
use crate::native::NativeFn;
|
||
use crate::realm::Realm;
|
||
use crate::vm_debug;
|
||
use crate::vm_state::{IterState, ProfilingState, SampleState};
|
||
use oxide_kernel::kernel::{KernelCore, KernelSession};
|
||
use oxide_types::error::JsErrorKind;
|
||
use oxide_types::object::{Cell, JsObject, JsString, NativeFnPtr};
|
||
use oxide_types::private_key::INT_KEY_COUNT;
|
||
use oxide_types::value::JsValue;
|
||
|
||
mod call;
|
||
mod dispatch_loop;
|
||
mod dynamic;
|
||
mod error;
|
||
mod frames;
|
||
mod gc_hooks;
|
||
mod inline;
|
||
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;
|
||
pub(crate) use inline::InlineSyncState;
|
||
pub(crate) use tables::TableGen;
|
||
|
||
/// 原型链解析深度上限:防超长或成环的原型链把属性查找拖入无界循环。
|
||
pub(crate) const MAX_PROTO_CHAIN_DEPTH: usize = 1024;
|
||
|
||
/// 判定字符串是否为规范数组下标(无前导零的纯数字串),并反解其值。
|
||
///
|
||
/// 命中则把该字符串键与对应整数键合并为同一键(`obj["5"]` == `obj[5]`)。
|
||
/// 只覆盖 `[0, INT_KEY_COUNT)`,更大的数字串(含 2^32 边界)走普通字符串键。
|
||
fn canonical_index_of(s: &str) -> Option<u32> {
|
||
if s.is_empty() {
|
||
return None;
|
||
}
|
||
let b = s.as_bytes();
|
||
if !b[0].is_ascii_digit() {
|
||
return None;
|
||
}
|
||
if b.len() > 1 && b[0] == b'0' {
|
||
return None;
|
||
}
|
||
if !b.iter().all(|c| c.is_ascii_digit()) {
|
||
return None;
|
||
}
|
||
let v: u32 = s.parse().ok()?;
|
||
if v < INT_KEY_COUNT {
|
||
Some(v)
|
||
} else {
|
||
None
|
||
}
|
||
}
|
||
|
||
/// 从单元序列判定字符串是否为规范数组下标(无前导零的纯 ASCII 数字串),
|
||
/// 并反解其值。口径与 [`canonical_index_of`] 一致,单元口径覆盖非 Flat
|
||
/// 载荷的键推导。只覆盖 `[0, INT_KEY_COUNT)`,更大的数字串走普通字符串键。
|
||
fn canonical_index_units(units: &[u16]) -> Option<u32> {
|
||
if units.is_empty() {
|
||
return None;
|
||
}
|
||
if !matches!(units[0], 0x30..=0x39) {
|
||
return None;
|
||
}
|
||
if units.len() > 1 && units[0] == 0x30 {
|
||
return None;
|
||
}
|
||
if units.len() > 10 {
|
||
return None;
|
||
}
|
||
let mut v: u32 = 0;
|
||
for &u in units.iter() {
|
||
let d = match u {
|
||
0x30..=0x39 => u - 0x30,
|
||
_ => return None,
|
||
};
|
||
// 两位都须 overflow 检查:`checked_mul(10) + d` 的裸加在 10 位串
|
||
// (如 "4294967296")会 mod 2^32 回绕出假规范下标。
|
||
v = v.checked_mul(10)?.checked_add(d as u32)?;
|
||
}
|
||
(v < INT_KEY_COUNT).then_some(v)
|
||
}
|
||
|
||
/// 将 [`NativeFnPtr`] 转换为可调用的 [`NativeFn`]。
|
||
///
|
||
/// # Safety
|
||
/// `ptr` 必须由合法的 `NativeFn` 函数项产生。
|
||
/// 这是代码库中唯一一处 `NativeFnPtr → NativeFn` 的强制转换点。
|
||
#[inline(always)]
|
||
pub(crate) unsafe fn native_fn_ptr_to_fn(ptr: NativeFnPtr) -> NativeFn {
|
||
std::mem::transmute::<*const (), NativeFn>(ptr.as_ptr())
|
||
}
|
||
|
||
fn js_error_kind(kind: &'static str) -> JsErrorKind {
|
||
match kind {
|
||
"TypeError" => JsErrorKind::TypeError,
|
||
"RangeError" => JsErrorKind::RangeError,
|
||
"ReferenceError" => JsErrorKind::ReferenceError,
|
||
"SyntaxError" => JsErrorKind::SyntaxError,
|
||
"URIError" => JsErrorKind::URIError,
|
||
"EvalError" => JsErrorKind::EvalError,
|
||
_ => JsErrorKind::Error,
|
||
}
|
||
}
|
||
|
||
fn js_error_kind_name(kind: JsErrorKind) -> &'static str {
|
||
match kind {
|
||
JsErrorKind::TypeError => "TypeError",
|
||
JsErrorKind::RangeError => "RangeError",
|
||
JsErrorKind::ReferenceError => "ReferenceError",
|
||
JsErrorKind::SyntaxError => "SyntaxError",
|
||
JsErrorKind::Error => "Error",
|
||
JsErrorKind::URIError => "URIError",
|
||
JsErrorKind::EvalError => "EvalError",
|
||
}
|
||
}
|
||
|
||
/// 按 name/msg 拼接错误文本:两者皆非空时为 `name: msg`,任一为空则取另一个。
|
||
pub(crate) fn format_error_message(name: &str, msg: &str) -> String {
|
||
if name.is_empty() {
|
||
msg.to_string()
|
||
} else if msg.is_empty() {
|
||
name.to_string()
|
||
} else {
|
||
format!("{name}: {msg}")
|
||
}
|
||
}
|
||
|
||
/// 逃出 for-await-of 的异步关闭在途状态:return() 的 promise 挂起等待结算,
|
||
/// 结算闭包(微任务)恢复挂起帧后继续关闭剩余条目并执行完成。
|
||
#[derive(Debug)]
|
||
pub(crate) struct PendingAsyncEscape {
|
||
/// 正在等待的 return() promise(结算闭包陈旧防御校验用)。
|
||
pub(crate) close_promise: JsValue,
|
||
/// 悬挂的完成(Break/Continue/Return),结算后执行。
|
||
pub(crate) completion: Completion,
|
||
/// 剩余待关闭的异步迭代器(LIFO 序,前元素先关)。单层路径恒空,多层路径填充。
|
||
pub(crate) remaining: Vec<JsValue>,
|
||
}
|
||
|
||
/// 基于寄存器的 JS 虚拟机:持有执行状态、寄存器文件、调用栈与 session 内存。
|
||
///
|
||
/// 执行入口为 [`Vm::run`](见 `vm_runtime` 模块);内存模型为统一入口分配 +
|
||
/// session 对象(可被 `SessionGc` 原地清扫回收,不搬移、地址稳定)+ session 字符串。多数内部字段为
|
||
/// `pub(crate)`,对外提供统计与内省 getter。
|
||
pub struct Vm {
|
||
pub(crate) regs: [JsValue; 256],
|
||
pub(crate) pc: usize,
|
||
/// 当前活动字节码。以 `Arc<[Instr]>` 共享:函数调用经 `Arc::clone` 换帧(O(1)),
|
||
/// 不再逐帧深拷贝。顶层装载期按引用计数条件复制(共享时显式深拷贝,独占时
|
||
/// 零拷贝),子模块平表源保持惰性 COW,IC 写回经 `bytecode_mut` 的
|
||
/// `Arc::make_mut` 写时复制,保证独占后才改写(miss 时才深拷贝,频率低)。
|
||
pub(crate) bytecode: Arc<[opcode::Instr]>,
|
||
/// 当前活动模块已转换不可变常量的只读视图(指向当前表代际的 immutables 内部)。
|
||
/// 用胖 `*const`:常量 Vec 归表代际所有且 OnceLock 只填一次(堆分配地址稳定)。
|
||
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 归
|
||
/// `Drop for Realm`(per-realm 消亡:Arc 计数归零时收尾)。
|
||
pub(crate) realm: Arc<Realm>,
|
||
/// `"length"` 属性键的 intern id 缓存:进程内稳定(PermInterner append-only、
|
||
/// KernelCore 不重建),属性 get/set 热路径免每次 intern(hash64 + DashMap +
|
||
/// RwLock 读锁)。
|
||
pub(crate) length_si: u32,
|
||
/// `"length"` 属性键 perm 串的内部指针:IC 站点的键寄存器恒为 perm 串
|
||
/// (发射层字符串常量统一经 perm_string 物化),指针相等即键为 "length",
|
||
/// 入口快判只比指针免键解析。PermInterner 追加式、指针唯一,永不失效。
|
||
pub(crate) length_perm_ptr: *const JsString,
|
||
/// f64→string 十六槽 last-value 缓存的键(double 位模式)。空槽判定用
|
||
/// 对应值非字符串:键 0 是 +0.0 的合法位模式,不得作空标记。
|
||
pub(crate) number_to_string_cache_keys: [u64; 16],
|
||
/// f64→string 十六槽 last-value 缓存的值(session 串,经 `for_each_value`
|
||
/// 登记为 GC 根;`full_reset` 在 session 串释放前清空)。
|
||
pub(crate) number_to_string_cache_vals: [JsValue; 16],
|
||
/// 微任务队列(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 时
|
||
/// notify 侧读同一指针恒匹配);run 边界清空,清位后 promise 无强根自然回收。
|
||
pub(crate) atomics_waiters: HashMap<(u64, usize), Vec<JsValue>>,
|
||
pub math_rng_state: u64,
|
||
/// 子模块平表的表代际注册表:键 = 表代际(`current_gen` 为当前 run 正在
|
||
/// 装载的代际)。函数对象创建时记录自身所属代际(`JsObject::table_gen`),
|
||
/// 调用期按创建期代际解析平表——跨 run 换表后存活函数仍命中原表。
|
||
/// 条目为 `Arc<CompiledModule>`,与调用方模块树共享:顶层条目即调用方模块
|
||
/// Arc 同一实例,`run()` 全程只做 Arc::clone,无深拷贝。run 边界回收无
|
||
/// 存活函数对象引用的代际(`reclaim_unreferenced_tables`),内存有界于
|
||
/// 存活函数对象数,不随 run 数单调增。
|
||
pub(crate) tables: HashMap<u32, Box<TableGen>>,
|
||
/// 当前 run 正在装载的表代际(构造器预登记 gen 0 空表占位,run() 换表时 +1)。
|
||
pub(crate) current_gen: u32,
|
||
/// 帧切换时暂存调用方字节码的 Arc 栈(与 `bytecode` 同共享语义)。
|
||
pub(crate) saved_bytecode_stack: Vec<Arc<[opcode::Instr]>>,
|
||
pub(crate) saved_immutables_stack: Vec<*const [JsValue]>,
|
||
/// 共享寄存器保存栈。每个活动 `CallFrame` 在 push 时把调用方活跃寄存器
|
||
/// (`regs[..caller_reg_limit]`)按 `saved_reg_offset` 存到这里;恢复时复制回
|
||
/// 并截断。容量跨调用保留,避免每次调用堆分配。
|
||
pub(crate) save_stack: Vec<JsValue>,
|
||
/// VM 级 spill 栈。`CallFrame.spill_offset` 定位本帧区:调用子函数时从边界后分配,
|
||
/// 帧恢复时截断到边界,子函数 spill 数据随帧丢弃。
|
||
pub(crate) spill_stack: Vec<JsValue>,
|
||
/// using/await using 声明的资源释放栈:DISPOSE_REGISTER 把(资源值,释放提示)
|
||
/// 压入,DISPOSE_POP 按水位逆序释放并截断。栈内 JsValue 是 GC 根
|
||
/// (`for_each_value` 遍历);run 边界清空,防跨 run 残留条目在下一 run 被误释放。
|
||
pub(crate) dispose_stack: Vec<(JsValue, u16)>,
|
||
/// 作用域水位栈:DISPOSE_MARK 压入当前释放栈深度,DISPOSE_POP 弹出并只释放
|
||
/// 水位以上条目。与释放栈同生共死(run 边界一并清空)。
|
||
pub(crate) dispose_marks: Vec<usize>,
|
||
/// 本次 native 调用的 spill 溢出实参区:`spill_stack[base..base+count)`。
|
||
/// 实参数超过寄存器窗口(253)时,窗口外的实参转存 spill 栈(GC 根),
|
||
/// native 侧经 `VmHost::native_arg_count`/`native_arg_at` 读取。仅在一次
|
||
/// native 调用期间有效,调用返回前截断回收。
|
||
pub(crate) native_overflow_base: usize,
|
||
pub(crate) native_overflow_count: usize,
|
||
pub(crate) try_stack: Vec<TryHandler>,
|
||
pub(crate) exception_value: Option<JsValue>,
|
||
/// 同步调用抛出的原始 JsValue 侧通道:`call_function_sync`/`unwind` 把错误展平为
|
||
/// String 后,for-of 需要重新抛出原值。放在 VM 顶层字段(不在 InlineSyncState 中)
|
||
/// 以便跨内联调用的恢复过程存活。
|
||
pub(crate) last_uncaught_value: Option<JsValue>,
|
||
/// 「正在求值模块」cyclic 守卫标记(deferred namespace 求值触发用):
|
||
/// `run` 在 dispatch 前置 `Some(undefined)`(入口模块哨兵)、`module_eval`
|
||
/// 在 `call_function_sync` 前置 `Some(fn)`,两者均 save/restore(嵌套安全)。
|
||
/// 触发点据此判自求值/祖先求值中的 cyclic 并抛 TypeError。
|
||
pub(crate) evaluating_module: Option<JsValue>,
|
||
/// 数组 length define 强转期用户代码抛出的原始异常:与 `last_uncaught_value`
|
||
/// 分离,避免入口误取其它操作忽略调用残留的值。仅 `define_array_length` 写入,
|
||
/// 由 Object/Reflect define 入口消费。
|
||
pub(crate) pending_length_exception: Option<JsValue>,
|
||
pub(crate) pending_exception: Option<JsValue>,
|
||
pub(crate) pending_error_kind: Option<&'static str>,
|
||
/// 控制流完成(break/continue/return)暂存,finally 执行后由 TRY_FINALLY_END 恢复。
|
||
pub(crate) pending_completion: Option<Completion>,
|
||
/// 逃出 for-await-of 的异步关闭在途状态:return() 的 promise 挂起等待结算,
|
||
/// 结算闭包(微任务)恢复挂起帧后继续关闭剩余条目并执行完成。
|
||
pub(crate) pending_async_escape: Option<PendingAsyncEscape>,
|
||
pub(crate) root_reg_limit: u8,
|
||
pub(crate) active_reg_limit: u8,
|
||
pub(crate) native_call_depth: usize,
|
||
/// JS 重入 hop 计数:native 体内(`native_call_depth > 0`)每次嵌套 dispatch
|
||
/// 进入计一跳。每 64 跳强制一次顶层分配上限采样——顶层 dispatch 冻结在 native
|
||
/// 体内时 `steps` 不推进、重入 dispatch 又太短到不了循环内采样点,分配上限
|
||
/// 否则结构性永不采样(小重入泵送盲区)。run 边界重置。
|
||
pub(crate) reentry_hops: u64,
|
||
/// inline 同步调用(`call_bytecode_function_inline`,frames 为空)的实参区位置。
|
||
/// frames 非空时 CREATE_ARGUMENTS 优先读当前帧的实参区;此字段只服务内联路径。
|
||
pub(crate) inline_args_base: u32,
|
||
pub(crate) inline_args_count: u16,
|
||
/// `ordinary_get` 压入字节码 accessor 帧时设为 Some(target_reg)。
|
||
/// 调度循环检查该标志,跳过用调用结果写 `regs[target_reg]` —— 值改由 RETURN
|
||
/// 处理器交付。
|
||
pub(crate) accessor_frame_target_reg: Option<u8>,
|
||
/// `call_bytecode_function_inline` 执行期间当前回调闭包(inline 同步调用语义)。
|
||
/// LOAD/STORE_UPVALUE 热路径直接读 `active_upvalues` 活动镜像,本字段仅经
|
||
/// `current_callee()` 服务冷路径:惰性建 cell 路径建 cell 时回写 callee 对象,
|
||
/// CREATE_CLOSURE 由此取父 upvalue 源。嵌套 inline 由 InlineSyncState 保存/恢复。
|
||
pub(crate) inline_callee: Option<JsValue>,
|
||
/// 当前执行函数的 upvalue cell 表(活动镜像,与 `CallFrame.upvalues` 同型:
|
||
/// 胖指针,非闭包为 null 空切片)。压帧/内联/弹帧/挂起恢复四个边界置位,
|
||
/// run 边界清零;指针恒有效——callee 对象是 GC 根,`upvalues` Box 创建后不
|
||
/// 替换,LOAD/STORE_UPVALUE 热路径直接按下标取 cell。
|
||
pub(crate) active_upvalues: *const [*mut Cell],
|
||
/// inline 目标函数的严格模式标志(与 `inline_callee` 同生命周期,
|
||
/// InlineSyncState 保存/恢复):内联执行期间帧表被隔离,写路径的
|
||
/// strict/sloppy 判定取此值而非帧标志。
|
||
pub(crate) inline_strict: bool,
|
||
/// inline 起始时 `frames.len()` 基线(随 InlineSyncState 保存/恢复):
|
||
/// 内联执行中新压的帧(CALL/accessor)越过该基线,其严格性归帧栈顶。
|
||
pub(crate) inline_frames_base: usize,
|
||
/// 本次 run 顶层脚本的严格模式标志(`module.is_strict`,run 时填充):
|
||
/// 无帧且无 inline(顶层脚本赋值)时写路径的 strict/sloppy 判定来源。
|
||
pub(crate) top_level_strict: bool,
|
||
/// 顶层 this 值(run 初始化时记录:脚本 = 全局对象,ES module = undefined)。
|
||
/// rerun 清空寄存器文件后据此恢复 regs[254]——不恢复则重执行时依赖 this
|
||
/// 的顶层写(顶层 var 全局同步写)在非对象 this 上静默 no-op。
|
||
pub(crate) top_level_this: JsValue,
|
||
/// inline 同步调用寄存器窗口缓冲池:`save_inline_state` 取出复用、
|
||
/// `restore_inline_state` 归还。热回调循环内 save/restore 反复使用同一块
|
||
/// 缓冲,只在嵌套(池已被外层取走)时新分配。
|
||
pub(crate) inline_reg_pool: Option<Vec<JsValue>>,
|
||
/// 外层 native pack 实参区上界(独占):`call_function_sync` native 分支在飞
|
||
/// 期间实参已 pack 进 `regs[0..pack_end)`,恢复边界(`restore_inline_state` /
|
||
/// `restore_frame`)的镜像重载须跳过该域——重载目标恰是外层 builtin 尚未
|
||
/// 读取的实参寄存器。0 = 无 pack 上下文;跨嵌套 native 调用存/还原
|
||
/// (与 overflow 描述符同构)。
|
||
pub(crate) native_pack_end: usize,
|
||
/// 生成器体 `dispatch()` 让出时的信号:YIELD 置 Some(让出值),恢复方(
|
||
/// generator 内嵌 dispatch 循环)取走并判定挂起。None = 正常返回/异常。
|
||
pub(crate) generator_suspended: Option<JsValue>,
|
||
/// `yield*` 委托中的内层迭代器:YIELD_STAR 让出时置入,恢复时转发 next/return/throw
|
||
/// 后按结局清空。随生成器挂起/恢复经 GeneratorState 传递(snapshot/rewrite 共管)。
|
||
pub(crate) delegated_iterator: Option<JsValue>,
|
||
/// 当前是否处于生成器内嵌 dispatch 循环:生成器帧弹出且 frames 清空时,
|
||
/// `do_return` 据此把结果交付给恢复方(而非当作普通顶层返回继续执行)。
|
||
/// 作用域限定于置位处包裹的那次 `dispatch()`:经 `InlineSyncState` 在
|
||
/// state-swap 边界保存/清零/恢复,嵌套内联调用不继承本标志。
|
||
pub(crate) generator_dispatch: bool,
|
||
/// 生成器调用时参数初始化步:body 起点标记(SUSPEND_BODY)据此判定"挂起在 body 前"。
|
||
pub(crate) generator_init_step: bool,
|
||
/// 参数初始化步中已越过 body 起点标记的信号(`initialize_generator` 消费后复位)。
|
||
pub(crate) generator_body_started: bool,
|
||
/// 当前正在执行的异步函数上下文对象(`OBJ_TYPE_ASYNC`,持有 AsyncState 快照)。
|
||
/// AWAIT dispatch 据此登记恢复反应;跨嵌套 async 调用保存/恢复。
|
||
pub(crate) async_context: Option<JsValue>,
|
||
/// 异步体 `dispatch()` 让出时的信号:AWAIT 置 true,恢复方(async 内嵌 dispatch
|
||
/// 循环)取走并快照挂起状态。false = 正常返回/异常。
|
||
pub(crate) async_suspended: bool,
|
||
/// 当前是否处于异步函数内嵌 dispatch 循环:异步帧弹出且 frames 清空时,
|
||
/// `do_return` 据此把结果交付给恢复方(与 generator_dispatch 同语义,
|
||
/// 同样经 InlineSyncState 限定作用域于属主 dispatch)。
|
||
pub(crate) async_dispatch: bool,
|
||
/// 当前是否处于构造器内嵌 dispatch 循环(`call_constructor_bytecode_inline`):
|
||
/// 构造帧弹出且 frames 清空时,`do_return` 据此把构造结果(regs[0],
|
||
/// 已做非对象回退 this)交付给恢复方(与 generator_dispatch 同语义,
|
||
/// 同样经 InlineSyncState 限定作用域于属主 dispatch)。
|
||
pub(crate) construct_dispatch: bool,
|
||
/// 当前 native 调用是否以构造形态发起(NEW/SUPER native 臂与
|
||
/// `construct_with` native 臂在调用前置 true,普通调用入口前置 false):
|
||
/// builtin 构造器(`typed_array_new`)据此判定向 receiver 物化,替代
|
||
/// 寄存器推断——native 调用与调用方共享寄存器文件,类构造器帧内的
|
||
/// new.target 槽会残留类构造器对象,按寄存器判定会把成员式普通调用
|
||
/// 误判为构造。调用结束即恢复/清零,不跨 native 调用存活。
|
||
pub(crate) constructing_native: bool,
|
||
/// 当前正在执行的异步生成器上下文对象(`OBJ_TYPE_ASYNC_GENERATOR`,持有
|
||
/// `AsyncGeneratorState` 快照)。AWAIT dispatch 据此登记异步生成器恢复反应;
|
||
/// 跨嵌套 async 调用保存/恢复(与 `async_context` 同生命周期,二者互斥占用)。
|
||
pub(crate) async_gen_context: Option<JsValue>,
|
||
/// 当前是否处于异步生成器内嵌 dispatch 循环:AWAIT 据此走异步生成器恢复
|
||
/// 闭包;yield 让出复用 `generator_suspended` 信号。
|
||
pub(crate) async_gen_dispatch: bool,
|
||
/// 异步生成器 body `dispatch()` 的 AWAIT 让出信号:置 true 表示挂起在 await,
|
||
/// 恢复方(异步生成器内嵌 dispatch 循环)据此快照挂起状态。
|
||
pub(crate) async_gen_suspended: bool,
|
||
/// 分组保存活跃的 for-in / for-of 迭代器状态。
|
||
pub(crate) iters: IterState,
|
||
/// 分组保存 inline cache 与指令计数器。
|
||
pub(crate) profiling: ProfilingState,
|
||
/// 分组保存指令周期采样状态(周期、top-K、样本记录)。
|
||
pub(crate) sampling: SampleState,
|
||
pub(crate) cell_stack: Vec<Vec<*mut Cell>>,
|
||
/// 标签模板对象缓存(GetTemplateObject):键 = (表代际, 模块 flat_id, site 序号)。
|
||
/// 同一代际同 flat_id 同 site 恒返回同一对象;每次 `run()` 清空(缓存只留
|
||
/// 本次 run 的条目,规模有界)。键含代际维度后,跨 run 调用旧代模块的
|
||
/// flat_id 重编号不再误命中。值为 GC 根(for_each_value 遍历)。
|
||
pub(crate) template_objects: HashMap<(u32, u32, u32), JsValue>,
|
||
/// 当前活动字节码所属模块的 flat_id(顶层 0;帧切换时随 bytecode 换)。
|
||
/// GET_TEMPLATE_OBJECT 据其区分不同编译树(eval 每次编译独立 site)。
|
||
pub(crate) active_flat_id: u32,
|
||
/// 当前活动字节码所属子模块平表的表代际(帧切换时随 flat_id 同步换)。
|
||
/// 模板缓存键含该代际:跨 run 调用的旧代模块与当前 run 模块 flat_id
|
||
/// 重编号互不碰撞。
|
||
pub(crate) active_table_gen: u32,
|
||
/// 帧切换时暂存调用方 flat_id 的栈(与 saved_bytecode_stack 同步 push/pop)。
|
||
pub(crate) saved_flat_id_stack: Vec<u32>,
|
||
/// 帧切换时暂存调用方表代际的栈(与 saved_flat_id_stack 同步 push/pop)。
|
||
pub(crate) saved_table_gen_stack: Vec<u32>,
|
||
/// 逐指令 trace 开关:开启时 dispatch 主循环对每条指令向 stderr 写一行
|
||
/// `pc + opcode + 操作数`。运行时开关,旁路 tracing 级别上限(release
|
||
/// 构建可用);默认关闭,关闭时热路径仅一次 bool 比较,零输出零分配。
|
||
pub trace_instructions: bool,
|
||
/// last-pc 现场文件路径:开启时 dispatch 主循环每 2^16 指令追加写一行
|
||
/// `pc/opcode/flat_id/frames`,供监督者超时/崩溃杀子进程后读回挂死点;
|
||
/// 默认 None(零输出零分配)。
|
||
pub(crate) pc_watch: Option<PathBuf>,
|
||
/// 每请求步数上限覆盖:执行路径按请求设置,None 回退内核配置。
|
||
/// 池回收的 `full_reset` 路径清位;`run()` 入口的 `clear_execution_state`
|
||
/// 刻意不清(清在那会把执行路径刚设的覆盖抹掉)。
|
||
pub(crate) max_steps_override: Option<u64>,
|
||
/// worker 注册表:键 = worker 编号,值 = `WorkerHandle`(主线程 → worker
|
||
/// 通道 + worker → 主线程通道 + OS 线程句柄)。worker 对象被 GC 不终止
|
||
/// worker 线程,条目孤儿至 `shutdown_workers()`(`Drop for Vm` 调用)。
|
||
pub(crate) worker_registry: std::collections::HashMap<u64, crate::worker::WorkerHandle>,
|
||
/// 下一个 worker 编号(单调递增,首个为 0)。
|
||
pub(crate) worker_next_id: u64,
|
||
/// Worker 对象注册表:键 = worker 编号,值 = Worker 对象(主 realm session
|
||
/// 对象)。主线程事件循环据编号反查 Worker 对象读 `onmessage`。Worker 对象
|
||
/// 是 GC 根(`for_each_value` 遍历),注册表保活至 `worker_terminate` /
|
||
/// `full_reset` 清表,无悬垂风险。
|
||
pub(crate) worker_objects: std::collections::HashMap<u64, JsValue>,
|
||
}
|
||
|
||
impl Drop for Vm {
|
||
fn drop(&mut self) {
|
||
// 先终止全部 worker 并 join(防线程泄漏):worker 线程各持自有 Vm,
|
||
// 其收尾独立于本 Vm,join 后线程不再引用本 Vm 的 kernel_core。
|
||
self.shutdown_workers();
|
||
// 边界守卫计数:与构造器登记恰好配对(Rust 所有权保证恰好一次)。
|
||
self.kernel_core.note_vm_ended();
|
||
// 活跃 for-in / for-of 迭代器是 per-VM 执行态:收尾路径逐条释放,
|
||
// 防 Vm 直接 drop 时表内残留体泄漏。
|
||
self.iters.reset();
|
||
// realm 收尾归 `Drop for Realm`(per-realm 消亡:Arc 计数归零时触发,
|
||
// 直接 drop 时恰好一次)。
|
||
}
|
||
}
|
||
|
||
// SAFETY: Vm 可安全跨线程移动(Send),依据四条不变量:
|
||
//
|
||
// 1. session 堆随 Vm 整体迁移:Vm 内全部裸指针(session 对象表
|
||
// session_object_ptrs、字符串表 session_string_ptrs、BigInt/cell 表、
|
||
// upvalue cell 指针、active_immutables 视图、length_perm_ptr)指向的堆对象
|
||
// 均由该 Vm 独占分配(Box::into_raw 统一入口),地址稳定、不随 Vm 移动而
|
||
// 搬移。跨线程移动 Vm 时指针恒有效,不产生悬垂。
|
||
//
|
||
// 2. &Vm 不跨线程共享:Vm 只经 &mut Vm 在单线程内访问(池化 VmGuard 独占
|
||
// 借出),RefCell(GcState 的 BigInt/cell 表、Realm 的 session/gc/P 字段)
|
||
// 与 Cell(profiling 计数)的非 Sync 性不构成跨线程数据竞争。
|
||
//
|
||
// 3. GC 根集不跨线程共享:每个 Vm 持有独立的 session 堆、独立的 mark/sweep
|
||
// 表与水位,无跨 Vm 的 GC 边。KernelCore 经 Arc<KernelCore> 共享
|
||
// (Send + Sync),是唯一的跨 Vm 共享面,其内部为 append-only forge 与
|
||
// 原子计数,无 per-VM 可变状态。
|
||
//
|
||
// 4. 裸指针不跨线程逃逸:Vm 内的裸指针只指向本 Vm 的 session 堆或共享 kernel
|
||
// 对象(perm 串 / shape / code forge),不指向其他 Vm 的堆。跨线程移动
|
||
// Vm 时这些指针的指向不变(堆对象地址稳定),不引入别名。
|
||
//
|
||
// realm 字段为 Arc<Realm>:Realm 的 session 堆不跨线程共享,Arc 引用计数为
|
||
// 原子操作,跨线程移动 Arc 安全;Realm 只经 Vm(单线程 &mut)访问,
|
||
// 其非 Sync 性对 Send 无碍。
|
||
//
|
||
// 5. worker_registry 字段(HashMap<u64, WorkerHandle>)不持 session 堆指针:
|
||
// WorkerHandle 的 tx(Sender<WorkerMail>)与 handle(JoinHandle<()>)均 Send,
|
||
// rx_out(Receiver<MessageValue>)虽非 Send 但只在主线程(Vm 属主线程)经
|
||
// poll_worker_messages 访问、不跨线程移动;MessageValue 是 Send 中间表示
|
||
// (无 realm 局部指针)。跨线程移动 Vm 时 worker_registry 整体迁移,
|
||
// 其中无指向 session 堆的裸指针,不引入别名。
|
||
//
|
||
// 6. worker_objects 字段(HashMap<u64, JsValue>)持本 Vm session 堆的 Worker
|
||
// 对象指针,归不变量 1 覆盖:对象由本 Vm 独占分配、地址稳定、随 Vm 整体
|
||
// 迁移,跨线程移动不悬垂。
|
||
unsafe impl Send for Vm {}
|
||
|
||
impl Vm {
|
||
const SYNC_NATIVE_ARG_BASE: usize = 0;
|
||
const SYNC_NATIVE_ARG_LIMIT: usize = 253;
|
||
|
||
/// 推进线性同余 RNG 一步(Math.random 用)。首次调用以系统时间纳秒播种。
|
||
pub fn step_rng(&mut self) {
|
||
if self.math_rng_state == 0 {
|
||
self.math_rng_state = std::time::SystemTime::now()
|
||
.duration_since(std::time::UNIX_EPOCH)
|
||
.unwrap_or_default()
|
||
.as_nanos() as u64;
|
||
vm_debug!("step_rng: seeded");
|
||
}
|
||
self.math_rng_state = self
|
||
.math_rng_state
|
||
.wrapping_mul(6364136223846793005)
|
||
.wrapping_add(1442695040888963407);
|
||
}
|
||
|
||
/// 读取当前 RNG 状态生成的 [0,1) 浮点数。
|
||
pub fn math_rng_value(&self) -> f64 {
|
||
(self.math_rng_state >> 33) as f64 / (1u64 << 31) as f64
|
||
}
|
||
|
||
/// 只读访问 VM 共享的 `KernelCore`。
|
||
pub fn kernel_core(&self) -> &Arc<KernelCore> {
|
||
&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` 后无法再给稳定 `&`):调用方
|
||
/// 在单表达式内消费,不跨 `borrow_mut` 长存。
|
||
pub fn session(&self) -> std::cell::Ref<'_, KernelSession> {
|
||
self.realm.session.borrow()
|
||
}
|
||
|
||
/// 读取本 VM 所属 realm 的编号(符号身份 = (realm 编号, 局部下标) 的 realm 维度)。
|
||
pub fn realm_id(&self) -> u32 {
|
||
self.realm.realm_id
|
||
}
|
||
|
||
/// 判定裸指针是否指向当前 session 的 `%Object.prototype%`。
|
||
pub(crate) fn is_object_prototype(&self, ptr: *const JsObject) -> bool {
|
||
let proto_ptr = self.realm.session.borrow().builtin_world().object_proto.as_ptr();
|
||
std::ptr::eq(ptr, proto_ptr)
|
||
}
|
||
|
||
/// 读取寄存器 `idx` 的值(`VmHost` 与 native 绑定的统一入口)。
|
||
pub fn reg(&self, idx: u8) -> JsValue {
|
||
self.regs[idx as usize]
|
||
}
|
||
|
||
/// 写入寄存器 `idx` 的值。
|
||
pub fn set_reg(&mut self, idx: u8, val: JsValue) {
|
||
self.regs[idx as usize] = val;
|
||
}
|
||
|
||
/// 设置逐指令 trace 开关(开启后 dispatch 主循环逐指令向 stderr 写
|
||
/// `pc + opcode + 操作数` 行,pc 为字节码 word 序号,与反汇编 offset 同单位)。
|
||
pub fn set_instruction_trace(&mut self, on: bool) {
|
||
self.trace_instructions = on;
|
||
}
|
||
|
||
/// 设置 last-pc 现场文件路径(开启后 dispatch 主循环每 2^16 指令追加写一行
|
||
/// `pc/opcode/flat_id/frames`,供监督者超时/崩溃杀子进程后读回挂死点);
|
||
/// None 清除。
|
||
pub fn set_pc_watch(&mut self, path: Option<PathBuf>) {
|
||
self.pc_watch = path;
|
||
}
|
||
|
||
/// 设置每请求步数上限覆盖:dispatch 主循环取 `覆盖值.or(内核配置)`,
|
||
/// 覆盖优先、None 回退内核配置。执行路径按请求必设(含 None),池回收的
|
||
/// `full_reset` 路径清位。
|
||
pub fn set_max_steps(&mut self, limit: Option<u64>) {
|
||
self.max_steps_override = limit;
|
||
}
|
||
|
||
/// 设置指令周期采样周期(2 的幂,0 关闭)。开启后 dispatch 主循环每
|
||
/// `period` 条指令记一条样本(flat_id、pc、opcode、frames 深度),run 末
|
||
/// 按 flat_id 聚合并向 stderr 输出 top-K 直方图;关闭时热路径仅一次
|
||
/// 可预测分支,零写零分配。
|
||
pub fn set_sample_period(&mut self, period: u64) {
|
||
self.sampling.period = period;
|
||
}
|
||
|
||
/// 设置采样直方图的 top-K 大小(默认 10)。
|
||
pub fn set_sample_top_k(&mut self, k: usize) {
|
||
self.sampling.top_k = k;
|
||
}
|
||
|
||
/// 若 `val` 是字符串,返回其内容的 `String` 副本;否则返回 `None`。
|
||
pub fn lookup_str(&self, val: JsValue) -> Option<String> {
|
||
if !val.is_string() {
|
||
return None;
|
||
}
|
||
// SAFETY: val 是字符串值,其 JsString 指针在生命周期内有效。
|
||
Some(unsafe { (*val.as_string_ptr()).to_owned_string() })
|
||
}
|
||
}
|
||
|
||
impl Default for Vm {
|
||
fn default() -> Self {
|
||
Self::new()
|
||
}
|
||
}
|
||
|
||
#[cfg(test)]
|
||
impl Vm {
|
||
/// 测试用:当前代际的子模块平表只读视图(消费点全在测试钉)。
|
||
pub(crate) fn current_table(&self) -> &TableGen {
|
||
self.tables.get(&self.current_gen).expect("当前代际表构造期已预登记")
|
||
}
|
||
|
||
/// 测试用:整表替换当前代际的子模块平表并同步常量缓存槽位(绕过 run() 装载,
|
||
/// 直接注入模块表后压帧/内联调用)。
|
||
pub(crate) fn install_module_table_for_test(&mut self, modules: Arc<Vec<Arc<CompiledModule>>>) {
|
||
let table = self.current_table_mut();
|
||
table.modules = modules;
|
||
table.immutables.resize(table.modules.len(), OnceLock::new());
|
||
table.si_tables.resize(table.modules.len(), OnceLock::new());
|
||
}
|
||
}
|
||
|
||
#[cfg(test)]
|
||
mod tests {
|
||
use super::{canonical_index_of, canonical_index_units};
|
||
use oxide_types::private_key::INT_KEY_COUNT;
|
||
|
||
fn units_of(s: &str) -> Vec<u16> {
|
||
s.chars().map(|c| c as u16).collect()
|
||
}
|
||
|
||
#[test]
|
||
fn canonical_index_str_boundaries() {
|
||
assert_eq!(canonical_index_of("0"), Some(0));
|
||
assert_eq!(canonical_index_of("1073741823"), Some(INT_KEY_COUNT - 1));
|
||
// 2^30 起不再是整数键候选;2^32 边界串(ToUint32 回绕源)必须走字符串键
|
||
assert_eq!(canonical_index_of("1073741824"), None);
|
||
assert_eq!(canonical_index_of("4294967295"), None);
|
||
assert_eq!(canonical_index_of("4294967296"), None);
|
||
assert_eq!(canonical_index_of("4294967299"), None);
|
||
// 非规范形态:前导零、符号、小数、空串
|
||
assert_eq!(canonical_index_of("007"), None);
|
||
assert_eq!(canonical_index_of("-1"), None);
|
||
assert_eq!(canonical_index_of("1.5"), None);
|
||
assert_eq!(canonical_index_of(""), None);
|
||
}
|
||
|
||
#[test]
|
||
fn canonical_index_units_boundaries() {
|
||
assert_eq!(canonical_index_units(&units_of("0")), Some(0));
|
||
assert_eq!(canonical_index_units(&units_of("1073741823")), Some(INT_KEY_COUNT - 1));
|
||
assert_eq!(canonical_index_units(&units_of("1073741824")), None);
|
||
// 10 位串在 u32 累加尾位溢出:回绕会造出假规范下标,须溢出检查归 None
|
||
assert_eq!(canonical_index_units(&units_of("4294967295")), None);
|
||
assert_eq!(canonical_index_units(&units_of("4294967296")), None);
|
||
assert_eq!(canonical_index_units(&units_of("4294967299")), None);
|
||
assert_eq!(canonical_index_units(&units_of("9999999999")), None);
|
||
assert_eq!(canonical_index_units(&units_of("007")), None);
|
||
assert_eq!(canonical_index_units(&units_of("-1")), None);
|
||
assert_eq!(canonical_index_units(&units_of("1.5")), None);
|
||
assert_eq!(canonical_index_units(&[]), None);
|
||
}
|
||
}
|