//! 基于寄存器的 JS 虚拟机:执行状态、寄存器文件、调用栈与 session 内存, //! 以及同步调用、指令主循环、异常通道、GC 钩子、属性解析等子模块的装配。 #![allow(clippy::arc_with_non_send_sync)] use std::collections::{HashMap, VecDeque}; 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::vm_debug; use crate::vm_state::{GcState, IterState, ProfilingState, SymbolState}; use oxide_kernel::kernel::{KernelCore, KernelSession}; use oxide_types::error::JsErrorKind; use oxide_types::mem::{Epoch, P}; use oxide_types::object::{Cell, JsObject, 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 frames::FrameArgs; pub use frames::{CallFrame, Completion, ForInIter, FrameContinuation, TryHandler}; 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 { 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 { 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}") } } /// 基于寄存器的 JS 虚拟机:持有执行状态、寄存器文件、调用栈与 session 内存。 /// /// 执行入口为 [`Vm::run`](见 `vm_runtime` 模块);内存模型为 epoch arena + /// session 对象(可被 `SessionGc` 移动式回收)+ session 字符串。多数内部字段为 /// `pub(crate)`,对外提供统计与内省 getter。 pub struct Vm { pub(crate) regs: [JsValue; 256], pub(crate) pc: usize, /// 当前活动字节码。以 `Arc<[Instr]>` 共享:函数调用经 `Arc::clone` 换帧(O(1)), /// 不再逐帧深拷贝。与子模块平表源共享同一缓冲,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, pub(crate) session: KernelSession, /// `"length"` 属性键的 intern id 缓存:进程内稳定(PermInterner append-only、 /// KernelCore 不重建),属性 get/set 热路径免每次 intern(hash64 + DashMap + /// RwLock 读锁)。 pub(crate) length_si: u32, pub epoch: Epoch, pub object_prototype: P, /// `%GeneratorPrototype%`:生成器实例的原型(next/return/throw 方法挂此)。 pub generator_proto: P, /// `%GeneratorFunction.prototype%`:生成器函数对象的原型(`constructor` 指向 /// `%GeneratorFunction%`,使 `g.constructor.name` 解析为 "GeneratorFunction")。 pub generator_function_proto: P, /// `%Promise%` 构造器(resolve/reject 静态方法挂此,global 的 Promise 槽指向它)。 pub promise_constructor: P, /// `%Promise.prototype%`:Promise 实例的原型(then/catch/finally 方法挂此)。 pub promise_proto: P, /// `%AggregateError%` 构造器(Promise.any 拒绝时构造 AggregateError 用)。 pub aggregate_error_constructor: P, /// `%AggregateError.prototype%`(proto = %Error.prototype%)。 pub aggregate_error_proto: P, /// `%AsyncFunction.prototype%`:异步函数对象的原型(`constructor` 指向 `%AsyncFunction%`)。 pub async_function_proto: P, /// `%AsyncGeneratorPrototype%`:异步生成器实例的原型(next/return/throw/@@asyncIterator)。 pub async_generator_proto: P, /// `%AsyncGeneratorFunction.prototype%`:异步生成器函数对象的原型。 pub async_generator_function_proto: P, /// 微任务队列(Promise reactions / thenable 委托),`run()` 末尾 FIFO drain。 pub(crate) job_queue: VecDeque, pub math_rng_state: u64, /// 子模块平表的表代际注册表:键 = 表代际(`current_gen` 为当前 run 正在 /// 装载的代际)。函数对象创建时记录自身所属代际(`JsObject::table_gen`), /// 调用期按创建期代际解析平表——跨 run 换表后存活函数仍命中原表。 /// 条目为 `Arc`,与调用方模块树共享:顶层条目即调用方模块 /// Arc 同一实例,`run()` 全程只做 Arc::clone,无深拷贝。run 边界回收无 /// 存活函数对象引用的代际(`reclaim_unreferenced_tables`),内存有界于 /// 存活函数对象数,不随 run 数单调增。 pub(crate) tables: HashMap>, /// 当前 run 正在装载的表代际(构造器预登记 gen 0 空表占位,run() 换表时 +1)。 pub(crate) current_gen: u32, /// 帧切换时暂存调用方字节码的 Arc 栈(与 `bytecode` 同共享语义)。 pub(crate) saved_bytecode_stack: Vec>, pub(crate) saved_immutables_stack: Vec<*const [JsValue]>, /// 共享寄存器保存栈。每个活动 `CallFrame` 在 push 时把调用方活跃寄存器 /// (`regs[..caller_reg_limit]`)按 `saved_reg_offset` 存到这里;恢复时复制回 /// 并截断。容量跨调用保留,避免每次调用堆分配。 pub(crate) save_stack: Vec, /// VM 级 spill 栈。`CallFrame.spill_offset` 定位本帧区:调用子函数时从边界后分配, /// 帧恢复时截断到边界,子函数 spill 数据随帧丢弃。 pub(crate) spill_stack: Vec, /// 本次 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, pub(crate) exception_value: Option, /// 同步调用抛出的原始 JsValue 侧通道:`call_function_sync`/`unwind` 把错误展平为 /// String 后,for-of 需要重新抛出原值。放在 VM 顶层字段(不在 InlineSyncState 中) /// 以便跨内联调用的恢复过程存活。 pub(crate) last_uncaught_value: Option, /// 数组 length define 强转期用户代码抛出的原始异常:与 `last_uncaught_value` /// 分离,避免入口误取其它操作忽略调用残留的值。仅 `define_array_length` 写入, /// 由 Object/Reflect define 入口消费。 pub(crate) pending_length_exception: Option, pub(crate) pending_exception: Option, pub(crate) pending_error_kind: Option<&'static str>, /// 控制流完成(break/continue/return)暂存,finally 执行后由 TRY_FINALLY_END 恢复。 pub(crate) pending_completion: Option, 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, /// `call_bytecode_function_inline` 执行期间当前回调闭包;LOAD/STORE_UPVALUE 在 /// frames 为空(inline 隔离状态)时由此取闭包 upvalues。嵌套 inline 由 /// InlineSyncState 保存/恢复。 pub(crate) inline_callee: Option, /// 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>, /// 外层 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, /// `yield*` 委托中的内层迭代器:YIELD_STAR 让出时置入,恢复时转发 next/return/throw /// 后按结局清空。随生成器挂起/恢复经 GeneratorState 传递(snapshot/rewrite 共管)。 pub(crate) delegated_iterator: Option, /// 当前是否处于生成器内嵌 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, /// 异步体 `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, /// 当前是否处于异步生成器内嵌 dispatch 循环:AWAIT 据此走异步生成器恢复 /// 闭包;yield 让出复用 `generator_suspended` 信号。 pub(crate) async_gen_dispatch: bool, /// 异步生成器 body `dispatch()` 的 AWAIT 让出信号:置 true 表示挂起在 await, /// 恢复方(异步生成器内嵌 dispatch 循环)据此快照挂起状态。 pub(crate) async_gen_suspended: bool, /// 分组保存 session arena / GC 簿记状态。 pub(crate) gc_state: GcState, /// 分组保存 `Symbol` intern 状态。 pub(crate) symbols: SymbolState, /// 分组保存活跃的 for-in / for-of 迭代器状态。 pub(crate) iters: IterState, /// 分组保存 inline cache 与指令计数器。 pub(crate) profiling: ProfilingState, pub(crate) cell_stack: Vec>, /// 标签模板对象缓存(GetTemplateObject):键 = (表代际, 模块 flat_id, site 序号)。 /// 同一代际同 flat_id 同 site 恒返回同一对象;每次 `run()` 清空(缓存只留 /// 本次 run 的条目,规模有界)。键含代际维度后,跨 run 调用旧代模块的 /// flat_id 重编号不再误命中。值为 GC 根(for_each_value/rewrite_values 遍历)。 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, /// 帧切换时暂存调用方表代际的栈(与 saved_flat_id_stack 同步 push/pop)。 pub(crate) saved_table_gen_stack: Vec, } impl Drop for Vm { fn drop(&mut self) { // 边界守卫计数:与构造器登记恰好配对(Rust 所有权保证恰好一次)。 self.kernel_core.note_vm_ended(); // 直接 drop(test262 每测试新建即弃)不经 reset/full_reset 路径: // 统一收尾释放全部 session 堆数据与内建原型属性区,防逐测试累积泄漏。 self.teardown_intrinsic_protos(); self.teardown_session_heap_data(); } } 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 { &self.kernel_core } /// 只读访问当前 session(builtin world 与 global object)。 pub fn session(&self) -> &KernelSession { &self.session } /// 判定裸指针是否指向当前 session 的 `%Object.prototype%`。 pub(crate) fn is_object_prototype(&self, ptr: *const JsObject) -> bool { let proto_ptr = self.session.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; } /// 只读访问 VM 的 epoch arena。 pub fn epoch(&self) -> &Epoch { &self.epoch } /// 若 `val` 是字符串,返回其内容的 `String` 副本;否则返回 `None`。 pub fn lookup_str(&self, val: JsValue) -> Option { 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>>) { let table = self.current_table_mut(); table.modules = modules; table.immutables.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 { 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); } }