diff --git a/crates/oxide_vm/src/vm/call.rs b/crates/oxide_vm/src/vm/call.rs new file mode 100644 index 00000000..9b6e028a --- /dev/null +++ b/crates/oxide_vm/src/vm/call.rs @@ -0,0 +1,280 @@ +//! 同步调用与压帧:native/字节码函数同步调用入口、大实参溢出区、 +//! 压帧窗口与形参回写、内联窗口上界与严格模式判定。 + +use std::sync::Arc; + +use oxide_runtime_api::NativeResult; +use oxide_types::object::JsObject; +use oxide_types::value::JsValue; + +use super::frames::{CallFrame, FrameArgs, FrameContinuation}; +use super::{native_fn_ptr_to_fn, Vm}; +use crate::native::NativeFn; +use crate::{vm_debug, vm_trace}; + +impl Vm { + fn pack_sync_native_call_args(&mut self, receiver: JsValue, callee: JsValue, args: &[JsValue]) -> Vec { + self.regs[253] = receiver; + self.regs[254] = callee; + + let mut arg_regs = Vec::with_capacity(args.len() + 1); + arg_regs.push(253); + for (idx, arg) in args.iter().enumerate() { + let reg = (Self::SYNC_NATIVE_ARG_BASE + idx) as u8; + self.regs[reg as usize] = *arg; + arg_regs.push(reg); + } + arg_regs + } + + pub(crate) fn is_session_ptr(&self, obj_ptr: *mut JsObject) -> bool { + if obj_ptr.is_null() { + return false; + } + // SAFETY: obj_ptr 非空且指向本 session 拥有的 JsObject。 + unsafe { (*obj_ptr).is_session_epoch() } + } + + pub(crate) fn alloc_object(&mut self, obj: JsObject) -> *mut JsObject { + let ptr = self.epoch.alloc(obj); + self.gc_state.track_epoch_object(ptr); + ptr + } + + pub(crate) fn call_function_sync( + &mut self, callee: JsValue, receiver: JsValue, args: &[JsValue], + ) -> Result { + vm_debug!("call_function_sync: args={} callee_is_object={}", args.len(), callee.is_object()); + if !callee.is_object() { + return Err(self.error_message_text("TypeError", "accessor is not callable")); + } + let callee_obj = unsafe { &*callee.as_js_object_ptr() }; + if !callee_obj.is_function() { + return Err(self.error_message_text("TypeError", "accessor is not callable")); + } + + if let Some(native_fn) = callee_obj.native_fn() { + 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()); + } + // 大实参集(超过寄存器窗口):窗口外的实参转存 spill 栈溢出区(GC 根), + // native 侧经 native_arg_count/native_arg_at 读取;窗口内仍按寄存器 + // 协议打包,保证未迁移的 builtin 行为不变。 + let overflow_base = self.spill_stack.len(); + let saved_overflow_base = self.native_overflow_base; + let saved_overflow_count = self.native_overflow_count; + if args.len() > Self::SYNC_NATIVE_ARG_LIMIT { + self.spill_stack.extend_from_slice(&args[Self::SYNC_NATIVE_ARG_LIMIT..]); + self.native_overflow_base = overflow_base; + self.native_overflow_count = args.len() - Self::SYNC_NATIVE_ARG_LIMIT; + } + // native 回调只写 regs[0..args.len()] 实参区 + regs[253]/[254](receiver/callee), + // 窗口 = 调用方活动寄存器 ∪ 实参写入区;窗口外槽回调不触碰,无需保存。 + let window = (self.active_reg_limit as usize).max(args.len() + 3).min(253); + let mut saved_window = self.inline_reg_pool.take().unwrap_or_default(); + saved_window.clear(); + saved_window.extend_from_slice(&self.regs[..window]); + let saved_r253 = self.regs[253]; + let saved_r254 = self.regs[254]; + let pack_limit = args.len().min(Self::SYNC_NATIVE_ARG_LIMIT); + let arg_regs = self.pack_sync_native_call_args(receiver, callee, &args[..pack_limit]); + // SAFETY: native_fn 经 set_native_fn 以合法 NativeFn 指针设置; + // native_fn_ptr_to_fn 是 NativeFnPtr → NativeFn 的唯一强制转换点。 + let func: NativeFn = unsafe { native_fn_ptr_to_fn(native_fn) }; + self.native_call_depth += 1; + let result = func(self, &arg_regs); + self.native_call_depth -= 1; + // 溢出区随本次调用结束截断回收,并还原外层的溢出区描述(嵌套调用安全)。 + if args.len() > Self::SYNC_NATIVE_ARG_LIMIT { + self.spill_stack.truncate(overflow_base); + } + self.native_overflow_base = saved_overflow_base; + self.native_overflow_count = saved_overflow_count; + // 窗口回拷 + regs[253]/[254] 单回,缓冲归还池复用。 + self.regs[..window].copy_from_slice(&saved_window); + self.regs[253] = saved_r253; + self.regs[254] = saved_r254; + self.inline_reg_pool = Some(saved_window); + return match result { + NativeResult::Ok(val) => Ok(val), + NativeResult::Err(err) => { + self.last_uncaught_value = Some(err); + Err(self.error_text(err)) + } + NativeResult::TailCall { callee, this, args } => self.call_function_sync(callee, this, &args), + }; + } + + // 字节码函数在自身(同一 epoch)内联执行,防止 use-after-free。 + // 独立子 VM 拥有不同 epoch:返回含子 VM epoch 指针的 JsValue 后再销毁子 VM, + // 会在 release 构建中产生悬垂指针 / 访问违规。 + self.call_bytecode_function_inline(callee, callee_obj, receiver, args) + } + + /// 压帧窗口上界:调用方活动寄存器与调用点存活上界取 min。 + /// + /// # 边界与前提 + /// - `call_window` 为 CALL ext 高 8 位编码的存活上界;0 = 未编码(回退全量)。 + /// - 窗口至少为 1(reg 0 恒为调用结果槽)。 + pub(crate) fn call_window_limit(&self, caller_active_reg_limit: u8, call_window: u8) -> u8 { + if call_window == 0 { + caller_active_reg_limit + } else { + caller_active_reg_limit.min(call_window) + } + .max(1) + } + + /// 当前执行上下文的严格模式标志:属性写失败路径据此分派(严格抛 + /// TypeError,sloppy 静默 no-op)。 + /// + /// # 边界与前提 + /// - inline 执行期间帧表可能非空(调用方帧隔离在外),须以 + /// `inline_frames_base` 为基线区分"inline 前已有帧"与"inline 内新压帧" + /// (CALL/accessor 帧严格性归目标函数,最内层执行上下文即帧栈顶)。 + /// - 无帧且无 inline 时为顶层脚本执行,取 `top_level_strict`。 + pub(crate) fn current_strict(&self) -> bool { + if self.inline_callee.is_some() { + if self.frames.len() > self.inline_frames_base { + return self.frames.last().unwrap().strict; + } + return self.inline_strict; + } + if let Some(frame) = self.frames.last() { + return frame.strict; + } + self.top_level_strict + } + + #[expect(clippy::too_many_arguments)] + pub(crate) fn push_bytecode_frame( + &mut self, callee: JsValue, this_value: JsValue, args: FrameArgs, construct_result_reg: Option, + constructed_this: Option, new_target: JsValue, continuation: FrameContinuation, call_window: u8, + ) -> Result<(), String> { + vm_trace!( + "push_bytecode_frame: depth={}, args={}, continuation={:?}", + self.frames.len(), + args.len(), + continuation + ); + if !callee.is_object() { + return Err(self.error_message_text("TypeError", "CALL target is not callable")); + } + let obj = unsafe { &*callee.as_js_object_ptr() }; + if !obj.is_function() || obj.sub_module_index() == 0 { + return Err(self.error_message_text("TypeError", "CALL target is not callable")); + } + let sub_idx = obj.sub_module_index() as usize; + let gen = obj.table_gen(); + let table = match self.tables.get(&gen) { + Some(t) => t, + None => return Err(format!("CALL: module table gen {} not available", gen)), + }; + if sub_idx >= table.modules.len() { + return Err(format!("CALL: sub_module_index {} out of bounds (max {})", sub_idx, table.modules.len())); + } + if self.frames.len() >= self.kernel_core.config.max_call_depth { + return self.raise_error_kind("RangeError", "Maximum call stack size exceeded"); + } + + // 被调模块取 Arc 克隆(与调用方共享同一编译产物):后续 activate_immutables + // 等 &mut self 调用期间不持有对注册表的借用。 + let sub = Arc::clone(&table.modules[sub_idx]); + let sub_bytecode = Arc::clone(&sub.bytecode); + let sub_n_args = sub.n_args as usize; + let sub_n_registers = sub.n_registers; + let sub_param_base = sub.param_base as usize; + let sub_is_arrow = sub.is_arrow; + let sub_is_strict = sub.is_strict; + // 窗口 = min(调用方活动寄存器, 存活上界);call_window=0 表示调用方全量 + // (运行时发起路径 / 未编码的旧模块)。恢复按窗口回拷,active_reg_limit + // 仍还原为调用方真实值(caller_active_reg_limit)。 + let caller_active_reg_limit = self.active_reg_limit.max(1); + let caller_reg_limit = self.call_window_limit(caller_active_reg_limit, call_window); + let saved_reg_offset = self.save_stack.len() as u32; + self.save_stack.extend_from_slice(&self.regs[..caller_reg_limit as usize]); + let saved_this = self.regs[254]; + let saved_new_target = self.regs[255]; + + // 完整实参先写入 spill 栈实参区(在帧的 spill 区之前):CREATE_ARGUMENTS 据此 + // 构建 arguments 对象,帧恢复时随 spill 区截断一起丢弃。spill 在前、形参在后, + // 且形参源改读 spill 区——实参源区间与形参写入区在共享寄存器文件内重叠时 + // 不会先写后读串值(nested callee identity 高位 param_base 可落入实参区间)。 + let args_base = self.spill_stack.len() as u32; + match args { + FrameArgs::Slice(s) => self.spill_stack.extend_from_slice(s), + FrameArgs::RegRange { first, count } => { + for i in 0..count { + self.spill_stack.push(self.regs[first.wrapping_add(i as u8) as usize]); + } + } + } + let args_count = args.len().min(u16::MAX as usize) as u16; + + // 形参拷贝:源为 spill 实参区(与调用方寄存器隔离),实参不足补 undefined。 + for i in 0..sub_n_args { + let v = if i < args_count as usize { + self.spill_stack[args_base as usize + i] + } else { + JsValue::undefined() + }; + self.regs[sub_param_base + i] = v; + } + // this 绑定:箭头函数恒用词法捕获;sloppy 普通函数 this 为 null/undefined 时 + // 替换为全局对象(ECMA-262 10.4.3);严格模式与显式方法/构造 this 原样保留。 + self.regs[254] = if sub_is_arrow { + obj.captured_this() + } else if !sub_is_strict && this_value.is_nullish() { + JsValue::from_js_object(self.session.global_object().as_ptr() as *mut JsObject) + } else { + this_value + }; + self.regs[255] = new_target; + + self.saved_bytecode_stack.push(std::mem::take(&mut self.bytecode)); + self.saved_immutables_stack.push(self.active_immutables); + // 记录调用方 flat_id 与表代际,进入被调模块(标签模板 site 缓存按 + // (代际, flat_id) 隔离)。 + self.saved_flat_id_stack.push(self.active_flat_id); + self.saved_table_gen_stack.push(self.active_table_gen); + self.active_flat_id = sub_idx as u32; + self.active_table_gen = gen; + + let function_name = sub + .function_name + .as_deref() + .map(|name| self.kernel_core.perm_interner().intern(name).0) + .unwrap_or(0); + + self.frames.push(CallFrame { + return_addr: self.pc, + function_name, + caller_reg_limit, + caller_active_reg_limit, + saved_reg_offset, + spill_offset: self.spill_stack.len() as u32, + arguments_base: args_base, + arguments_count: args_count, + saved_this, + saved_new_target, + callee, + construct_result_reg, + constructed_this, + is_derived_constructor: obj.is_derived_constructor(), + super_called: false, + strict: sub_is_strict, + continuation, + }); + + self.pc = 0; + self.bytecode = sub_bytecode; + self.activate_immutables(gen, sub_idx, &sub.constants); + self.cell_stack.push(Vec::with_capacity(sub.cells_needed as usize)); + self.reload_builtin_mirror_slots(&sub.builtin_reg_map); + + self.active_reg_limit = sub_n_registers.max(1); + self.pc = 0; + Ok(()) + } +} diff --git a/crates/oxide_vm/src/vm/error.rs b/crates/oxide_vm/src/vm/error.rs new file mode 100644 index 00000000..94991d90 --- /dev/null +++ b/crates/oxide_vm/src/vm/error.rs @@ -0,0 +1,219 @@ +//! 错误抛出与有界强转:强转失败统一出口、调用错误恢复、错误构造与异常 +//! 展开、有界数值强转三件套与抛出错误 kind 查询。 + +use oxide_runtime_api as coercion; + +use oxide_types::error::JsError; +use oxide_types::object::JsObject; +use oxide_types::value::{JsValue, PTR_MASK}; + +use super::{format_error_message, js_error_kind, js_error_kind_name, Vm}; +use crate::vm_debug; + +impl Vm { + pub(crate) fn coerce_primitive_bounded(&mut self, value: JsValue, prefer_string: bool) -> Result { + if !value.is_object() { + return Ok(value); + } + + let obj_ptr = value.as_js_object_ptr(); + if obj_ptr.is_null() { + return Ok(value); + } + + // ECMA-262 §7.1.1 step 1: an exotic obj[Symbol.toPrimitive] takes precedence + // over OrdinaryToPrimitive. well-known symbol 键经 property_key_si 映射为 + // 固定 Symbol 键,读键路径与写键路径一致。 + let sym_key = { + let sym_ptr = self.session.builtin_world().sym_to_primitive.as_ptr() as *mut JsObject; + JsValue::from_js_object(sym_ptr) + }; + let sym_si = self.property_key_si(sym_key)?; + let exotic = { + let obj = unsafe { &*obj_ptr }; + self.ordinary_get(obj, sym_si, value)? + }; + if !exotic.is_undefined() && !exotic.is_null() { + let exotic_ptr = exotic.as_js_object_ptr(); + if !exotic.is_object() || exotic_ptr.is_null() || !unsafe { &*exotic_ptr }.is_function() { + // 抛可捕获的 JS 异常(dispatch 层 try/catch 可捕获),见 conversion_error。 + self.conversion_error("Symbol.toPrimitive is not a function")?; + return Ok(JsValue::undefined()); + } + let hint_val = self.new_string(if prefer_string { "string" } else { "number" }); + let result = match self.call_function_sync(exotic, value, &[hint_val]) { + Ok(r) => r, + Err(err) => return self.raise_call_error(&err), + }; + if result.is_object() { + self.conversion_error("Cannot convert object to primitive value")?; + return Ok(JsValue::undefined()); + } + return Ok(result); + } + + let method_names = if prefer_string { ["toString", "valueOf"] } else { ["valueOf", "toString"] }; + + for method_name in method_names { + let method_si = self.kernel_core.perm_interner().intern(method_name).0; + let method = { + let obj = unsafe { &*obj_ptr }; + self.ordinary_get(obj, method_si, value)? + }; + if method.is_undefined() || method.is_null() { + continue; + } + // OrdinaryToPrimitive:valueOf/toString 不可调用时跳过(IsCallable == false + // 则 continue),不抛错;仅 @@toPrimitive 不可调用时抛 TypeError。 + if !method.is_object() { + continue; + } + let method_ptr = method.as_js_object_ptr(); + if method_ptr.is_null() || !unsafe { &*method_ptr }.is_function() { + continue; + } + + let result = match self.call_function_sync(method, value, &[]) { + Ok(r) => r, + Err(err) => return self.raise_call_error(&err), + }; + if !result.is_object() { + return Ok(result); + } + } + + // 主 dispatch 抛可捕获异常(外围 JS try/catch 可捕获);原生 builtin 内部 + // 只传播格式化 Err,由其调用边界恢复为异常对象(见 conversion_error)。 + self.conversion_error("Cannot convert object to primitive value")?; + Ok(JsValue::undefined()) + } + + /// 对象转原始值失败时的统一出口:主 dispatch(native_call_depth == 0)下抛可捕获 + /// 的 JS 异常并就地展开到外围 try/catch;原生 builtin 内部(depth > 0)不得就地 + /// 展开(展开会消费 try 处理器,builtin 却继续执行并可能产生二次错误),改为返回 + /// 格式化 Err,由原生调用边界(call_function_sync → dispatch_native_call)恢复为 + /// 原始异常对象。 + #[inline(always)] + fn conversion_error(&mut self, msg: &str) -> Result<(), String> { + if self.native_call_depth == 0 { + self.raise_error_kind("TypeError", msg) + } else { + Err(self.error_message_text("TypeError", msg)) + } + } + + /// 把 `call_function_sync` 返回的调用错误恢复为原始异常值并走异常展开, + /// 使外围 try/catch 可捕获(native 函数抛错时原值存于 `last_uncaught_value`)。 + /// + /// # 注意事项 + /// 仅在主 dispatch(`native_call_depth == 0`)下展开——此时 try_stack 只含当前 + /// 字节码的处理器,展开后 pc/regs[0] 不会被中途的原生调用栈覆盖。原生 builtin + /// 内部(depth > 0)必须传播错误,由其调用边界(dispatch_native_call)转换。 + pub(crate) fn raise_call_error(&mut self, err: &str) -> Result { + if self.native_call_depth == 0 { + let exc = self + .last_uncaught_value + .take() + .unwrap_or_else(|| oxide_builtins::error::create_from_text(self, err)); + let kind = self.thrown_error_kind(exc); + self.exception_value = Some(exc); + self.pending_error_kind = Some(kind); + self.unwind()?; + return Ok(JsValue::undefined()); + } + Err(err.to_string()) + } + + pub(crate) fn coerce_number_bounded(&mut self, value: JsValue) -> Result { + let primitive = self.coerce_primitive_bounded(value, false)?; + Ok(coercion::to_number(primitive)) + } + + pub(crate) fn coerce_int32_bounded(&mut self, value: JsValue) -> Result { + if value.is_int() { + return Ok(value.as_int()); + } + let n = self.coerce_number_bounded(value)?; + if n == 0.0 || !n.is_finite() { + return Ok(0); + } + let int = n.trunc().rem_euclid(4_294_967_296.0) as u32; + if int > i32::MAX as u32 { + Ok((int as i64 - 4_294_967_296i64) as i32) + } else { + Ok(int as i32) + } + } + + pub(crate) fn coerce_uint32_bounded(&mut self, value: JsValue) -> Result { + if value.is_int() { + return Ok(value.as_int() as u32); + } + let n = self.coerce_number_bounded(value)?; + if n == 0.0 || !n.is_finite() { + return Ok(0); + } + Ok(n.trunc().rem_euclid(4_294_967_296.0) as u32) + } + + pub(crate) fn checked_object_ptr( + &mut self, val: JsValue, error_msg: &str, + ) -> Result, String> { + if !val.is_object() { + self.raise_type_error(error_msg)?; + return Ok(None); + } + let ptr = (val.to_bits() & PTR_MASK) as *mut JsObject; + let addr = ptr as usize; + if ptr.is_null() || addr < 0x10000 || addr % std::mem::align_of::() != 0 { + self.raise_type_error(error_msg)?; + return Ok(None); + } + Ok(Some(ptr)) + } + + pub(crate) fn raise_error_kind(&mut self, kind: &'static str, msg: &str) -> Result<(), String> { + self.raise_js_error(JsError::new(js_error_kind(kind), msg)) + } + + pub(crate) fn raise_js_error(&mut self, err: JsError) -> Result<(), String> { + let kind = js_error_kind_name(err.kind); + vm_debug!("raise_js_error: {} \"{}\"", kind, err.message); + let error = oxide_builtins::error::create_kind_error(self, kind, &err.message); + self.exception_value = Some(error); + self.pending_error_kind = Some(kind); + self.unwind() + } + + pub(crate) fn raise_type_error(&mut self, msg: &str) -> Result<(), String> { + self.raise_error_kind("TypeError", msg) + } + + pub(crate) fn error_message_text(&self, kind: &str, msg: &str) -> String { + format_error_message(kind, msg) + } + + pub(crate) fn thrown_error_kind(&self, val: JsValue) -> &'static str { + if !val.is_object() { + return "Error"; + } + let name_si = self.kernel_core.perm_interner().intern("name").0; + let obj = unsafe { &*val.as_js_object_ptr() }; + let Some(name_val) = self.resolve_property(obj, name_si) else { + return "Error"; + }; + let Some(name) = self.lookup_str(name_val) else { + return "Error"; + }; + match name.as_str() { + "TypeError" => "TypeError", + "ReferenceError" => "ReferenceError", + "RangeError" => "RangeError", + "SyntaxError" => "SyntaxError", + "URIError" => "URIError", + "EvalError" => "EvalError", + "Error" => "Error", + _ => "Error", + } + } +} diff --git a/crates/oxide_vm/src/vm/gc_hooks.rs b/crates/oxide_vm/src/vm/gc_hooks.rs new file mode 100644 index 00000000..3f1f2f83 --- /dev/null +++ b/crates/oxide_vm/src/vm/gc_hooks.rs @@ -0,0 +1,373 @@ +//! GC 根遍历与执行期收集钩子:根统一枚举、搬移后指针重写、执行期两档 +//! 收集入口(安全点门控)与 session GC 账目统计访问器。 + +use oxide_types::object::{Cell, JsObject}; +use oxide_types::value::JsValue; + +use super::frames::Completion; +use super::Vm; +use crate::session_gc::SessionGc; + +impl Vm { + /// GC 根收集的统一遍历(对象与字符串都产出)。与 `rewrite_values` 字段一一对应。 + /// 覆盖执行核心的全部 JsValue 持有点:regs/帧/各栈段/cell/在途异常与完成/ + /// 挂起信号/迭代器/微任务/global。新增执行字段必须同时登记在此与 + /// `rewrite_values`。 + pub(crate) fn for_each_value(&self, mut f: impl FnMut(JsValue)) { + for value in &self.regs { + f(*value); + } + // 各代际 immutables 缓存含 session BigInt(new_bigint 分配),未入根则被 + // sweep 释放 → 常量池加载时悬垂。perm 字符串无害(不在 session 集合中)。 + for table in self.tables.values() { + for once_lock in &table.immutables { + if let Some(immutable_vec) = once_lock.get() { + for &value in immutable_vec.iter() { + f(value); + } + } + } + } + for frame in &self.frames { + f(frame.saved_this); + f(frame.saved_new_target); + f(frame.callee); + f(frame.constructed_this.unwrap_or(JsValue::undefined())); + } + for &v in &self.save_stack { + f(v); + } + // spill 栈是 session GC 根(漏根 → 溢出值被回收 → use-after-free)。 + for &v in &self.spill_stack { + f(v); + } + for cell_vec in &self.cell_stack { + for &cell_ptr in cell_vec { + if cell_ptr.is_null() { + continue; + } + // SAFETY: cell 由 session_epoch 分配,本 session 内指针有效。 + f(unsafe { &*cell_ptr }.value); + } + } + f(self.exception_value.unwrap_or(JsValue::undefined())); + f(self.pending_exception.unwrap_or(JsValue::undefined())); + f(self.last_uncaught_value.unwrap_or(JsValue::undefined())); + // 悬挂的 return 完成持有返回值,是 GC 根。 + if let Some(Completion::Return { value, .. }) = self.pending_completion { + f(value); + } + f(self.generator_suspended.unwrap_or(JsValue::undefined())); + f(self.delegated_iterator.unwrap_or(JsValue::undefined())); + f(self.async_context.unwrap_or(JsValue::undefined())); + f(self.async_gen_context.unwrap_or(JsValue::undefined())); + f(self.inline_callee.unwrap_or(JsValue::undefined())); + // 标签模板对象缓存:命中的模板对象是 GC 根(未根 → sweep 搬移/回收悬垂)。 + for &cached in self.template_objects.values() { + f(cached); + } + for &entry in &self.iters.for_of_iters { + f(entry.iterator); + f(entry.last_result); + } + // 微任务队列中的处理器/能力/值都是 GC 根。 + for job in &self.job_queue { + crate::promise::for_each_job_value(job, &mut f); + } + for iter in &self.iters.for_in_iters { + if iter.is_null() { + continue; + } + // SAFETY: for_in_iters 存放由当前 VM epoch 拥有的存活迭代器指针。 + unsafe { + for (v, _si) in (*(*iter)).keys.iter() { + f(*v); + } + } + } + f(JsValue::from_js_object(self.session.global_object().as_ptr() as *mut JsObject)); + } + + /// GC 指针重写(session 搬移后调用)。与 `for_each_value` 字段一一对应。 + pub(crate) fn rewrite_values(&mut self, mut rewrite: impl FnMut(JsValue) -> JsValue) { + for value in &mut self.regs { + *value = rewrite(*value); + } + for frame in &mut self.frames { + frame.saved_this = rewrite(frame.saved_this); + frame.saved_new_target = rewrite(frame.saved_new_target); + frame.callee = rewrite(frame.callee); + frame.constructed_this = frame.constructed_this.map(&mut rewrite); + } + for v in &mut self.save_stack { + *v = rewrite(*v); + } + for v in &mut self.spill_stack { + *v = rewrite(*v); + } + for cell_vec in &mut self.cell_stack { + for &mut cell_ptr in cell_vec.iter_mut() { + if cell_ptr.is_null() { + continue; + } + // SAFETY: cell 由 session_epoch 分配,本 session 内指针有效。 + let cell = unsafe { &mut *cell_ptr }; + cell.value = rewrite(cell.value); + } + } + self.exception_value = self.exception_value.map(&mut rewrite); + self.pending_exception = self.pending_exception.map(&mut rewrite); + self.last_uncaught_value = self.last_uncaught_value.map(&mut rewrite); + for cached in self.template_objects.values_mut() { + *cached = rewrite(*cached); + } + self.pending_completion = self.pending_completion.map(|completion| match completion { + Completion::Return { + value, + remaining_finally, + for_of_count, + for_in_count, + } => Completion::Return { + value: rewrite(value), + remaining_finally, + for_of_count, + for_in_count, + }, + other => other, + }); + self.generator_suspended = self.generator_suspended.map(&mut rewrite); + self.delegated_iterator = self.delegated_iterator.map(&mut rewrite); + self.async_context = self.async_context.map(&mut rewrite); + self.async_gen_context = self.async_gen_context.map(&mut rewrite); + self.inline_callee = self.inline_callee.map(&mut rewrite); + for entry in &mut self.iters.for_of_iters { + entry.iterator = rewrite(entry.iterator); + entry.last_result = rewrite(entry.last_result); + } + // 微任务队列中的值随 sweep 重写。 + for job in &mut self.job_queue { + crate::promise::rewrite_job_values(job, &mut rewrite); + } + for iter in &mut self.iters.for_in_iters { + if iter.is_null() { + continue; + } + // SAFETY: for_in_iters 存放由当前 VM epoch 拥有的存活迭代器指针。 + unsafe { + for (v, _si) in (*(*iter)).keys.iter_mut() { + *v = rewrite(*v); + } + } + } + let global_ptr = self.session.global_object().as_ptr() as *mut JsObject; + if !global_ptr.is_null() { + // SAFETY: KernelSession 在 VM 生命周期内拥有 global_object。 + unsafe { + (*global_ptr).rewrite_object_values(rewrite); + } + } + } + + pub(crate) fn for_each_root(&self, f: impl FnMut(JsValue)) { + // 统一遍历:根收集与指针重写共用同一字段清单。 + self.for_each_value(f); + } + + pub(crate) fn maybe_collect_session_gc(&mut self) { + let mut session_gc = std::mem::take(&mut self.gc_state.session_gc); + session_gc.maybe_collect(self); + self.gc_state.session_gc = session_gc; + } + + /// 执行期字符串阈值回收:仅回收 session 字符串(跳过对象搬移)。热路径只在 + /// 超阈值后进入,`mem::take` 不承担每次分配的开销。 + pub(crate) fn maybe_collect_session_strings(&mut self) { + let mut session_gc = std::mem::take(&mut self.gc_state.session_gc); + session_gc.maybe_collect_strings_only(self); + self.gc_state.session_gc = session_gc; + } + + /// 门控子句二:三型状态盒(生成器/异步函数/异步生成器)的 + /// `suspended.for_in_iters` 是否非空。 + /// + /// ForInIter body 分配于 epoch arena,收集换新 Bump 即时失效在表迭代器; + /// 挂起帧把迭代器搬入状态盒(`vm.iters` 不可见),故须逐状态盒扫描。 + /// + /// # 边界与前提 + /// - 扫描 epoch + session 两份对象表——挂起状态盒宿主对象可能尚未晋升; + /// - 挂起帧的 keys 经状态盒边收为 GC 根(被枚举对象不误释放),悬的是 + /// 迭代器 body,故按指针扫 `for_in_iters`; + /// - 保守口径:死对象的状态盒同样计入(持挂起 for-in 的 run 放弃执行期 + /// 收集),正确性优先于收益; + /// - 新增挂起态持有者须在此登记。 + pub(crate) fn suspended_holds_for_in(&self) -> bool { + let holds = |&ptr: &*mut JsObject| { + if ptr.is_null() { + return false; + } + // SAFETY: 对象表登记的指针,arena 存活期内有效。 + let obj = unsafe { &*ptr }; + match obj.type_tag { + JsObject::OBJ_TYPE_GENERATOR => crate::generator::generator_holds_suspended_for_in(obj), + JsObject::OBJ_TYPE_ASYNC => crate::async_func::async_holds_suspended_for_in(obj), + JsObject::OBJ_TYPE_ASYNC_GENERATOR => { + crate::async_generator::async_generator_holds_suspended_for_in(obj) + } + _ => false, + } + }; + self.gc_state.epoch_object_ptrs.iter().any(holds) || self.gc_state.session_object_ptrs.iter().any(holds) + } + + /// 执行期两档收集的 dispatch 安全点入口:仅在循环顶 + /// (`native_call_depth == 0`,无 builtin 局部裸指针、dispatch 未重入) + /// 由触发块调用;先门控后收集。 + /// + /// # 边界与前提 + /// - 子句一(活跃 for-in,O(1))未过:免费重试,不设锚; + /// - 子句二(状态盒扫描,O(对象数))未过:重扫按包络增长一个阈值设锚, + /// 避免持挂起 for-in 的 run 每指令边界重扫; + /// - 门控过但无对象可回收时仍跑一轮(mark + 换新 Bump),水位同点抬高, + /// 触发间距由包络增量控制。 + pub(crate) fn maybe_collect_in_run(&mut self) { + if !self.iters.for_in_iters.is_empty() { + return; + } + if self.gc_state.gc_gate_retry_alloc > self.run_alloc_bytes() { + return; + } + if self.suspended_holds_for_in() { + self.gc_state.gc_gate_retry_alloc = + self.run_alloc_bytes().saturating_add(self.gc_state.gc_threshold_cached); + return; + } + let mut session_gc = std::mem::take(&mut self.gc_state.session_gc); + session_gc.collect_in_run(self); + self.gc_state.session_gc = session_gc; + } + + /// 只读访问 session GC 的统计(回收次数、存活/死亡对象数、释放字节等)。 + pub fn session_gc_stats(&self) -> &SessionGc { + &self.gc_state.session_gc + } + + /// 当前 session arena 中存活(已晋升)的对象数量。 + pub fn session_object_count(&self) -> usize { + self.gc_state.session_object_ptrs.len() + } + + /// session 当前分配的字节数(对象 + 存活字符串)。 + pub fn session_bytes_allocated(&self) -> usize { + self.gc_state.session_bytes_allocated + } + + /// 执行期 session 堆账目的峰值高水位(顶层指令边界采样,全量重置清零)。 + pub fn session_bytes_peak(&self) -> usize { + self.gc_state.session_bytes_peak + } + + /// 双 arena(epoch + session 对象)当前 chunk 已分配字节之和。 + /// + /// # 注意事项 + /// reset 边界读 = 换新 Bump 后空 arena 恰 0(空 chunk 哨兵计 0); + /// run 中途读 = 当前 chunk 用量(非全 arena 口径——run 计量走 + /// [`Self::run_alloc_bytes`],勿据此断言)。 + pub fn arena_retained_bytes(&self) -> usize { + self.epoch.bump().allocated_bytes() + self.gc_state.session_epoch.allocated_bytes() + } + + /// 本 run 累计分配字节的高水位:`run_alloc_bytes` 的顶层指令边界 + /// 采样上界,run 边界(reset/full_reset)重起算。留存内存观测锚。 + pub fn run_alloc_peak(&self) -> usize { + self.gc_state.run_alloc_peak + } + + /// 本 run 累计分配字节:epoch arena + session 对象 arena + session 手工堆 + /// 账目(session 串 + session 对象及其属性向量 + GC 后补回的 BigInt)。 + /// 单次 run 内单调不减(执行期对象不回收、 + /// 串 GC 只降手工堆账目而 arena 不减);run 边界(reset)后重新起算, + /// 供单 run 分配上限判定。 + /// + /// 注意:手工堆账目只在 promote/字符串分配/GC 回收点更新,执行期对象 + /// 属性区(元素/属性向量扩容)增长对其不可见——上限判定须配合 + /// [`Self::run_alloc_bytes_full`] 的深采样层。 + pub(crate) fn run_alloc_bytes(&self) -> usize { + self.epoch.bump().allocated_bytes() + + self.gc_state.session_epoch.allocated_bytes() + + self.gc_state.session_bytes_allocated + } + + /// 本 run 累计分配字节的全量重算版:base 只取两个 arena 计数器 + /// (epoch + session 对象 arena),手工堆逐一重算——已登记对象的堆数据 + /// (属性/元素向量 + upvalue 列表 + native 状态盒)、session 串、BigInt + /// 与 upvalue cell 的容量。三分量(arena / 对象堆数据 / 串-BigInt-cell) + /// 两两不相交,且不含 session 手工堆账目——无交叠不双计,重算即属性区 + /// 扩容等账目盲区的兜底。 + pub(crate) fn run_alloc_bytes_full(&self) -> u64 { + let mut bytes = (self.epoch.bump().allocated_bytes() + self.gc_state.session_epoch.allocated_bytes()) as u64; + for &ptr in self + .gc_state + .epoch_object_ptrs + .iter() + .chain(self.gc_state.session_object_ptrs.iter()) + { + if ptr.is_null() { + continue; + } + // SAFETY: ptr 来自对象表登记,dispatch 安全点处仍有效。 + bytes += SessionGc::object_heap_data_bytes(unsafe { &*ptr }); + } + for &ptr in &self.gc_state.session_string_ptrs { + // SAFETY: ptr 来自字符串表登记,收尾前始终有效。 + bytes += (std::mem::size_of::() + unsafe { (*ptr).payload_bytes() }) as u64; + } + bytes += (self.gc_state.session_bigint_ptrs.borrow().len() * std::mem::size_of::()) as u64; + bytes += (self.gc_state.session_cell_ptrs.borrow().len() * std::mem::size_of::()) as u64; + bytes + } + + /// 无条件执行一次完整 session GC(mark + 移动式 sweep + 串/BigInt 清扫)。 + /// + /// # 副作用 + /// - 存活对象复制进新 session arena,全部根与原生盒按转发表重写; + /// `session_bytes_allocated` 重置为清扫后的存活字节。 + /// + /// # 注意事项 + /// - 须在执行外的安全点调用(无在途 builtin 局部裸指针、dispatch 未重入); + /// 执行期触发仍走水位路径,本入口供事后观测(如基准测 workload 后留存堆)。 + pub fn collect_session_gc(&mut self) { + let mut session_gc = std::mem::take(&mut self.gc_state.session_gc); + session_gc.collect(self); + self.gc_state.session_gc = session_gc; + } + + /// 当前 epoch 中已分配并跟踪的对象数量(未晋升到 session 的临时对象)。 + pub fn epoch_object_count(&self) -> usize { + self.gc_state.epoch_object_ptrs.len() + } + + /// inline cache 命中率(0.0~1.0),用于观测 IC 预热效果。 + pub fn ic_hit_rate(&self) -> f64 { + self.profiling.ic_hit_rate() + } + + /// 累计执行的指令数(profiling)。 + pub fn instruction_count(&self) -> u64 { + self.profiling.instruction_count + } + + /// 全局 symbol 注册表中已注册的 key 数量。 + pub fn symbol_registry_len(&self) -> usize { + self.symbols.registry_len() + } + + /// inline cache 命中次数。 + pub fn ic_hit_count(&self) -> u64 { + self.profiling.ic_hits.get() + } + + /// inline cache 未命中次数。 + pub fn ic_miss_count(&self) -> u64 { + self.profiling.ic_misses.get() + } +} diff --git a/crates/oxide_vm/src/vm/mod.rs b/crates/oxide_vm/src/vm/mod.rs index dbf3b425..9c02dced 100644 --- a/crates/oxide_vm/src/vm/mod.rs +++ b/crates/oxide_vm/src/vm/mod.rs @@ -4,31 +4,31 @@ use std::collections::{HashMap, VecDeque}; use std::sync::{Arc, OnceLock}; use num_traits::Zero; -use oxide_bytecode::module::{CompiledModule, Constant}; +use oxide_bytecode::module::CompiledModule; use oxide_bytecode::opcode::{self, OpCode}; use smallvec::SmallVec; /// 初始化一个 session 的内置对象(global 槽位、各构造器与原型、IC 预热)。 pub use crate::bindings::init_kernel_builtins; use crate::native::NativeFn; -use crate::session_gc::SessionGc; #[cfg(test)] use crate::vm_state::ForOfEntry; use crate::vm_state::{GcState, IterState, ProfilingState, SymbolState}; use crate::{vm_debug, vm_error, vm_trace, vm_warn}; use oxide_kernel::kernel::{KernelCore, KernelSession}; use oxide_runtime_api as coercion; -use oxide_runtime_api::NativeResult; -use oxide_types::error::{JsError, JsErrorKind}; +use oxide_types::error::JsErrorKind; use oxide_types::mem::{Epoch, P}; use oxide_types::object::{Cell, JsObject, NativeFnPtr, PropAttributes}; -use oxide_types::private_key::{ - int_key_value, is_int_key, make_int_key, make_symbol_key, make_well_known_symbol_key, INT_KEY_COUNT, -}; -use oxide_types::value::{JsValue, PTR_MASK}; +use oxide_types::private_key::INT_KEY_COUNT; +use oxide_types::value::JsValue; +mod call; +mod error; mod frames; +mod gc_hooks; mod inline; +mod props; mod tables; pub(crate) use frames::FrameArgs; @@ -394,663 +394,6 @@ impl Vm { const SYNC_NATIVE_ARG_BASE: usize = 0; const SYNC_NATIVE_ARG_LIMIT: usize = 253; - pub(crate) fn coerce_primitive_bounded(&mut self, value: JsValue, prefer_string: bool) -> Result { - if !value.is_object() { - return Ok(value); - } - - let obj_ptr = value.as_js_object_ptr(); - if obj_ptr.is_null() { - return Ok(value); - } - - // ECMA-262 §7.1.1 step 1: an exotic obj[Symbol.toPrimitive] takes precedence - // over OrdinaryToPrimitive. well-known symbol 键经 property_key_si 映射为 - // 固定 Symbol 键,读键路径与写键路径一致。 - let sym_key = { - let sym_ptr = self.session.builtin_world().sym_to_primitive.as_ptr() as *mut JsObject; - JsValue::from_js_object(sym_ptr) - }; - let sym_si = self.property_key_si(sym_key)?; - let exotic = { - let obj = unsafe { &*obj_ptr }; - self.ordinary_get(obj, sym_si, value)? - }; - if !exotic.is_undefined() && !exotic.is_null() { - let exotic_ptr = exotic.as_js_object_ptr(); - if !exotic.is_object() || exotic_ptr.is_null() || !unsafe { &*exotic_ptr }.is_function() { - // 抛可捕获的 JS 异常(dispatch 层 try/catch 可捕获),见 conversion_error。 - self.conversion_error("Symbol.toPrimitive is not a function")?; - return Ok(JsValue::undefined()); - } - let hint_val = self.new_string(if prefer_string { "string" } else { "number" }); - let result = match self.call_function_sync(exotic, value, &[hint_val]) { - Ok(r) => r, - Err(err) => return self.raise_call_error(&err), - }; - if result.is_object() { - self.conversion_error("Cannot convert object to primitive value")?; - return Ok(JsValue::undefined()); - } - return Ok(result); - } - - let method_names = if prefer_string { ["toString", "valueOf"] } else { ["valueOf", "toString"] }; - - for method_name in method_names { - let method_si = self.kernel_core.perm_interner().intern(method_name).0; - let method = { - let obj = unsafe { &*obj_ptr }; - self.ordinary_get(obj, method_si, value)? - }; - if method.is_undefined() || method.is_null() { - continue; - } - // OrdinaryToPrimitive:valueOf/toString 不可调用时跳过(IsCallable == false - // 则 continue),不抛错;仅 @@toPrimitive 不可调用时抛 TypeError。 - if !method.is_object() { - continue; - } - let method_ptr = method.as_js_object_ptr(); - if method_ptr.is_null() || !unsafe { &*method_ptr }.is_function() { - continue; - } - - let result = match self.call_function_sync(method, value, &[]) { - Ok(r) => r, - Err(err) => return self.raise_call_error(&err), - }; - if !result.is_object() { - return Ok(result); - } - } - - // 主 dispatch 抛可捕获异常(外围 JS try/catch 可捕获);原生 builtin 内部 - // 只传播格式化 Err,由其调用边界恢复为异常对象(见 conversion_error)。 - self.conversion_error("Cannot convert object to primitive value")?; - Ok(JsValue::undefined()) - } - - /// 对象转原始值失败时的统一出口:主 dispatch(native_call_depth == 0)下抛可捕获 - /// 的 JS 异常并就地展开到外围 try/catch;原生 builtin 内部(depth > 0)不得就地 - /// 展开(展开会消费 try 处理器,builtin 却继续执行并可能产生二次错误),改为返回 - /// 格式化 Err,由原生调用边界(call_function_sync → dispatch_native_call)恢复为 - /// 原始异常对象。 - #[inline(always)] - fn conversion_error(&mut self, msg: &str) -> Result<(), String> { - if self.native_call_depth == 0 { - self.raise_error_kind("TypeError", msg) - } else { - Err(self.error_message_text("TypeError", msg)) - } - } - - /// 把 `call_function_sync` 返回的调用错误恢复为原始异常值并走异常展开, - /// 使外围 try/catch 可捕获(native 函数抛错时原值存于 `last_uncaught_value`)。 - /// - /// # 注意事项 - /// 仅在主 dispatch(`native_call_depth == 0`)下展开——此时 try_stack 只含当前 - /// 字节码的处理器,展开后 pc/regs[0] 不会被中途的原生调用栈覆盖。原生 builtin - /// 内部(depth > 0)必须传播错误,由其调用边界(dispatch_native_call)转换。 - pub(crate) fn raise_call_error(&mut self, err: &str) -> Result { - if self.native_call_depth == 0 { - let exc = self - .last_uncaught_value - .take() - .unwrap_or_else(|| oxide_builtins::error::create_from_text(self, err)); - let kind = self.thrown_error_kind(exc); - self.exception_value = Some(exc); - self.pending_error_kind = Some(kind); - self.unwind()?; - return Ok(JsValue::undefined()); - } - Err(err.to_string()) - } - - pub(crate) fn coerce_number_bounded(&mut self, value: JsValue) -> Result { - let primitive = self.coerce_primitive_bounded(value, false)?; - Ok(coercion::to_number(primitive)) - } - - pub(crate) fn coerce_int32_bounded(&mut self, value: JsValue) -> Result { - if value.is_int() { - return Ok(value.as_int()); - } - let n = self.coerce_number_bounded(value)?; - if n == 0.0 || !n.is_finite() { - return Ok(0); - } - let int = n.trunc().rem_euclid(4_294_967_296.0) as u32; - if int > i32::MAX as u32 { - Ok((int as i64 - 4_294_967_296i64) as i32) - } else { - Ok(int as i32) - } - } - - pub(crate) fn coerce_uint32_bounded(&mut self, value: JsValue) -> Result { - if value.is_int() { - return Ok(value.as_int() as u32); - } - let n = self.coerce_number_bounded(value)?; - if n == 0.0 || !n.is_finite() { - return Ok(0); - } - Ok(n.trunc().rem_euclid(4_294_967_296.0) as u32) - } - - fn pack_sync_native_call_args(&mut self, receiver: JsValue, callee: JsValue, args: &[JsValue]) -> Vec { - self.regs[253] = receiver; - self.regs[254] = callee; - - let mut arg_regs = Vec::with_capacity(args.len() + 1); - arg_regs.push(253); - for (idx, arg) in args.iter().enumerate() { - let reg = (Self::SYNC_NATIVE_ARG_BASE + idx) as u8; - self.regs[reg as usize] = *arg; - arg_regs.push(reg); - } - arg_regs - } - - pub(crate) fn is_session_ptr(&self, obj_ptr: *mut JsObject) -> bool { - if obj_ptr.is_null() { - return false; - } - // SAFETY: obj_ptr 非空且指向本 session 拥有的 JsObject。 - unsafe { (*obj_ptr).is_session_epoch() } - } - - pub(crate) fn alloc_object(&mut self, obj: JsObject) -> *mut JsObject { - let ptr = self.epoch.alloc(obj); - self.gc_state.track_epoch_object(ptr); - ptr - } - - /// 活动模块已转换不可变常量的只读视图;任何 `run()` 之前为空。 - #[inline(always)] - pub(crate) fn immutables(&self) -> &[JsValue] { - if self.active_immutables.is_null() { - &[] - } else { - // SAFETY: active_immutables 指向某表代际 immutables 内 OnceLock 的常量 - // Vec,Vec 只填充一次且堆地址稳定;所指代际表在注册表内存活(当前 - // 代际恒在,旧代际由存活函数对象引用保活)。 - unsafe { &*self.active_immutables } - } - } - - /// 当前代际的子模块平表(可变,动态扩表与测试注入的目标)。 - pub(crate) fn current_table_mut(&mut self) -> &mut TableGen { - self.tables.get_mut(&self.current_gen).expect("当前代际表构造期已预登记") - } - - /// 当前执行帧所属代际的子模块平表:按帧切换时记录的 `active_table_gen` - /// 解析(压帧 / inline 入口 / run 顶层 / 挂起恢复置位,弹帧还原),跨 run - /// 调用时可异于 `current_gen`——帧内指令操作数是帧字节码所属代际平表里的 - /// flat_id,须按该代际解析。 - /// - /// # 边界与前提 - /// - 执行帧代际表恒在注册表中:顶层即当前代际(恒保留),帧 / inline 的 - /// 代际由存活 callee 函数对象保活,挂起恢复已按 callee 对象验证在位。 - pub(crate) fn active_table(&self) -> &TableGen { - self.tables.get(&self.active_table_gen).expect("执行帧的代际表须在注册表中") - } - - /// 函数对象 `sub_module_index` 指向的子模块条目:按对象自身记录的表代际 - /// 解析平表后按下标定位。 - /// - /// # 边界与前提 - /// - 原生函数哨兵 `sub_module_index() == 0` 不经本方法(调用方先行分派); - /// - 代际表已被回收或下标越界返回 None(调用方按各自越界口径报错)。 - pub(crate) fn callee_module<'a>(&'a self, obj: &'a JsObject) -> Option<&'a CompiledModule> { - self.tables - .get(&obj.table_gen()) - .and_then(|t| t.modules.get(obj.sub_module_index() as usize)) - .map(|m| m.as_ref()) - } - - /// 活动帧所属代际平表里 `active_flat_id` 指向的模块条目(镜像槽同步 / - /// 重载的名集载体)。 - /// - /// # 边界与前提 - /// - 活动代际表在执行期恒在注册表中(首 run 前为预登记的空表占位), - /// `active_flat_id` 越界返回 None(调用方按无模块处理)。 - pub(crate) fn active_module(&self) -> Option<&CompiledModule> { - self.active_table() - .modules - .get(self.active_flat_id as usize) - .map(|m| m.as_ref()) - } - - /// 子模块表注册表的当前代际条目总数(run 边界回收行为的测试钉)。 - pub fn table_gen_count(&self) -> usize { - self.tables.len() - } - - /// 激活代际 `gen` 平表中模块 `cache_idx` 的不可变常量,只转换一次存入该代际 - /// `immutables[cache_idx]`,并把 `active_immutables` 指向该 Vec。 - /// `constants` 由调用方传入(它已持有 `&module.constants`)。 - /// - /// # 边界与前提 - /// - `gen` 的表须已在注册表中(当前代际由 run() 登记,旧代际由存活函数 - /// 对象引用保活);`cache_idx` 须小于该代际平表长度。 - pub(crate) fn activate_immutables(&mut self, gen: u32, cache_idx: usize, constants: &[Constant]) { - // 用裸指针访问缓存槽,避免 get_or_init(借用表 immutables)与 &self 的 - // convert_immutables 闭包和随后对 active_immutables 的写入发生借用冲突。 - // 成立前提:代际表归注册表所有、只读、Box 承载地址稳定。 - let table = self.tables.get_mut(&gen).expect("激活的代际表须在注册表中"); - let slot: *const OnceLock> = &table.immutables[cache_idx]; - let vec = unsafe { &*slot }.get_or_init(|| self.convert_immutables(constants)); - self.active_immutables = vec.as_slice() as *const [JsValue]; - } - - /// 当前活动字节码的可变访问入口。bytecode 以 `Arc<[Instr]>` 与代际表 modules 源共享, - /// IC 写回经 `Arc::make_mut` 保证独占:独占时零拷贝原地写,共享时先深拷贝再写 - /// (IC miss 才触发,频率低)。所有写操作必须经此方法,防止共享缓冲被多实例污染。 - pub(crate) fn bytecode_mut(&mut self) -> &mut [opcode::Instr] { - Arc::make_mut(&mut self.bytecode) - } - - /// GC 根收集的统一遍历(对象与字符串都产出)。与 `rewrite_values` 字段一一对应。 - /// 覆盖执行核心的全部 JsValue 持有点:regs/帧/各栈段/cell/在途异常与完成/ - /// 挂起信号/迭代器/微任务/global。新增执行字段必须同时登记在此与 - /// `rewrite_values`。 - pub(crate) fn for_each_value(&self, mut f: impl FnMut(JsValue)) { - for value in &self.regs { - f(*value); - } - // 各代际 immutables 缓存含 session BigInt(new_bigint 分配),未入根则被 - // sweep 释放 → 常量池加载时悬垂。perm 字符串无害(不在 session 集合中)。 - for table in self.tables.values() { - for once_lock in &table.immutables { - if let Some(immutable_vec) = once_lock.get() { - for &value in immutable_vec.iter() { - f(value); - } - } - } - } - for frame in &self.frames { - f(frame.saved_this); - f(frame.saved_new_target); - f(frame.callee); - f(frame.constructed_this.unwrap_or(JsValue::undefined())); - } - for &v in &self.save_stack { - f(v); - } - // spill 栈是 session GC 根(漏根 → 溢出值被回收 → use-after-free)。 - for &v in &self.spill_stack { - f(v); - } - for cell_vec in &self.cell_stack { - for &cell_ptr in cell_vec { - if cell_ptr.is_null() { - continue; - } - // SAFETY: cell 由 session_epoch 分配,本 session 内指针有效。 - f(unsafe { &*cell_ptr }.value); - } - } - f(self.exception_value.unwrap_or(JsValue::undefined())); - f(self.pending_exception.unwrap_or(JsValue::undefined())); - f(self.last_uncaught_value.unwrap_or(JsValue::undefined())); - // 悬挂的 return 完成持有返回值,是 GC 根。 - if let Some(Completion::Return { value, .. }) = self.pending_completion { - f(value); - } - f(self.generator_suspended.unwrap_or(JsValue::undefined())); - f(self.delegated_iterator.unwrap_or(JsValue::undefined())); - f(self.async_context.unwrap_or(JsValue::undefined())); - f(self.async_gen_context.unwrap_or(JsValue::undefined())); - f(self.inline_callee.unwrap_or(JsValue::undefined())); - // 标签模板对象缓存:命中的模板对象是 GC 根(未根 → sweep 搬移/回收悬垂)。 - for &cached in self.template_objects.values() { - f(cached); - } - for &entry in &self.iters.for_of_iters { - f(entry.iterator); - f(entry.last_result); - } - // 微任务队列中的处理器/能力/值都是 GC 根。 - for job in &self.job_queue { - crate::promise::for_each_job_value(job, &mut f); - } - for iter in &self.iters.for_in_iters { - if iter.is_null() { - continue; - } - // SAFETY: for_in_iters 存放由当前 VM epoch 拥有的存活迭代器指针。 - unsafe { - for (v, _si) in (*(*iter)).keys.iter() { - f(*v); - } - } - } - f(JsValue::from_js_object(self.session.global_object().as_ptr() as *mut JsObject)); - } - - /// GC 指针重写(session 搬移后调用)。与 `for_each_value` 字段一一对应。 - pub(crate) fn rewrite_values(&mut self, mut rewrite: impl FnMut(JsValue) -> JsValue) { - for value in &mut self.regs { - *value = rewrite(*value); - } - for frame in &mut self.frames { - frame.saved_this = rewrite(frame.saved_this); - frame.saved_new_target = rewrite(frame.saved_new_target); - frame.callee = rewrite(frame.callee); - frame.constructed_this = frame.constructed_this.map(&mut rewrite); - } - for v in &mut self.save_stack { - *v = rewrite(*v); - } - for v in &mut self.spill_stack { - *v = rewrite(*v); - } - for cell_vec in &mut self.cell_stack { - for &mut cell_ptr in cell_vec.iter_mut() { - if cell_ptr.is_null() { - continue; - } - // SAFETY: cell 由 session_epoch 分配,本 session 内指针有效。 - let cell = unsafe { &mut *cell_ptr }; - cell.value = rewrite(cell.value); - } - } - self.exception_value = self.exception_value.map(&mut rewrite); - self.pending_exception = self.pending_exception.map(&mut rewrite); - self.last_uncaught_value = self.last_uncaught_value.map(&mut rewrite); - for cached in self.template_objects.values_mut() { - *cached = rewrite(*cached); - } - self.pending_completion = self.pending_completion.map(|completion| match completion { - Completion::Return { - value, - remaining_finally, - for_of_count, - for_in_count, - } => Completion::Return { - value: rewrite(value), - remaining_finally, - for_of_count, - for_in_count, - }, - other => other, - }); - self.generator_suspended = self.generator_suspended.map(&mut rewrite); - self.delegated_iterator = self.delegated_iterator.map(&mut rewrite); - self.async_context = self.async_context.map(&mut rewrite); - self.async_gen_context = self.async_gen_context.map(&mut rewrite); - self.inline_callee = self.inline_callee.map(&mut rewrite); - for entry in &mut self.iters.for_of_iters { - entry.iterator = rewrite(entry.iterator); - entry.last_result = rewrite(entry.last_result); - } - // 微任务队列中的值随 sweep 重写。 - for job in &mut self.job_queue { - crate::promise::rewrite_job_values(job, &mut rewrite); - } - for iter in &mut self.iters.for_in_iters { - if iter.is_null() { - continue; - } - // SAFETY: for_in_iters 存放由当前 VM epoch 拥有的存活迭代器指针。 - unsafe { - for (v, _si) in (*(*iter)).keys.iter_mut() { - *v = rewrite(*v); - } - } - } - let global_ptr = self.session.global_object().as_ptr() as *mut JsObject; - if !global_ptr.is_null() { - // SAFETY: KernelSession 在 VM 生命周期内拥有 global_object。 - unsafe { - (*global_ptr).rewrite_object_values(rewrite); - } - } - } - - pub(crate) fn for_each_root(&self, f: impl FnMut(JsValue)) { - // 统一遍历:根收集与指针重写共用同一字段清单。 - self.for_each_value(f); - } - - pub(crate) fn maybe_collect_session_gc(&mut self) { - let mut session_gc = std::mem::take(&mut self.gc_state.session_gc); - session_gc.maybe_collect(self); - self.gc_state.session_gc = session_gc; - } - - /// 执行期字符串阈值回收:仅回收 session 字符串(跳过对象搬移)。热路径只在 - /// 超阈值后进入,`mem::take` 不承担每次分配的开销。 - pub(crate) fn maybe_collect_session_strings(&mut self) { - let mut session_gc = std::mem::take(&mut self.gc_state.session_gc); - session_gc.maybe_collect_strings_only(self); - self.gc_state.session_gc = session_gc; - } - - /// 门控子句二:三型状态盒(生成器/异步函数/异步生成器)的 - /// `suspended.for_in_iters` 是否非空。 - /// - /// ForInIter body 分配于 epoch arena,收集换新 Bump 即时失效在表迭代器; - /// 挂起帧把迭代器搬入状态盒(`vm.iters` 不可见),故须逐状态盒扫描。 - /// - /// # 边界与前提 - /// - 扫描 epoch + session 两份对象表——挂起状态盒宿主对象可能尚未晋升; - /// - 挂起帧的 keys 经状态盒边收为 GC 根(被枚举对象不误释放),悬的是 - /// 迭代器 body,故按指针扫 `for_in_iters`; - /// - 保守口径:死对象的状态盒同样计入(持挂起 for-in 的 run 放弃执行期 - /// 收集),正确性优先于收益; - /// - 新增挂起态持有者须在此登记。 - pub(crate) fn suspended_holds_for_in(&self) -> bool { - let holds = |&ptr: &*mut JsObject| { - if ptr.is_null() { - return false; - } - // SAFETY: 对象表登记的指针,arena 存活期内有效。 - let obj = unsafe { &*ptr }; - match obj.type_tag { - JsObject::OBJ_TYPE_GENERATOR => crate::generator::generator_holds_suspended_for_in(obj), - JsObject::OBJ_TYPE_ASYNC => crate::async_func::async_holds_suspended_for_in(obj), - JsObject::OBJ_TYPE_ASYNC_GENERATOR => { - crate::async_generator::async_generator_holds_suspended_for_in(obj) - } - _ => false, - } - }; - self.gc_state.epoch_object_ptrs.iter().any(holds) || self.gc_state.session_object_ptrs.iter().any(holds) - } - - /// 执行期两档收集的 dispatch 安全点入口:仅在循环顶 - /// (`native_call_depth == 0`,无 builtin 局部裸指针、dispatch 未重入) - /// 由触发块调用;先门控后收集。 - /// - /// # 边界与前提 - /// - 子句一(活跃 for-in,O(1))未过:免费重试,不设锚; - /// - 子句二(状态盒扫描,O(对象数))未过:重扫按包络增长一个阈值设锚, - /// 避免持挂起 for-in 的 run 每指令边界重扫; - /// - 门控过但无对象可回收时仍跑一轮(mark + 换新 Bump),水位同点抬高, - /// 触发间距由包络增量控制。 - pub(crate) fn maybe_collect_in_run(&mut self) { - if !self.iters.for_in_iters.is_empty() { - return; - } - if self.gc_state.gc_gate_retry_alloc > self.run_alloc_bytes() { - return; - } - if self.suspended_holds_for_in() { - self.gc_state.gc_gate_retry_alloc = - self.run_alloc_bytes().saturating_add(self.gc_state.gc_threshold_cached); - return; - } - let mut session_gc = std::mem::take(&mut self.gc_state.session_gc); - session_gc.collect_in_run(self); - self.gc_state.session_gc = session_gc; - } - - /// 只读访问 session GC 的统计(回收次数、存活/死亡对象数、释放字节等)。 - pub fn session_gc_stats(&self) -> &SessionGc { - &self.gc_state.session_gc - } - - /// 当前 session arena 中存活(已晋升)的对象数量。 - pub fn session_object_count(&self) -> usize { - self.gc_state.session_object_ptrs.len() - } - - /// session 当前分配的字节数(对象 + 存活字符串)。 - pub fn session_bytes_allocated(&self) -> usize { - self.gc_state.session_bytes_allocated - } - - /// 执行期 session 堆账目的峰值高水位(顶层指令边界采样,全量重置清零)。 - pub fn session_bytes_peak(&self) -> usize { - self.gc_state.session_bytes_peak - } - - /// 双 arena(epoch + session 对象)当前 chunk 已分配字节之和。 - /// - /// # 注意事项 - /// reset 边界读 = 换新 Bump 后空 arena 恰 0(空 chunk 哨兵计 0); - /// run 中途读 = 当前 chunk 用量(非全 arena 口径——run 计量走 - /// [`Self::run_alloc_bytes`],勿据此断言)。 - pub fn arena_retained_bytes(&self) -> usize { - self.epoch.bump().allocated_bytes() + self.gc_state.session_epoch.allocated_bytes() - } - - /// 本 run 累计分配字节的高水位:`run_alloc_bytes` 的顶层指令边界 - /// 采样上界,run 边界(reset/full_reset)重起算。留存内存观测锚。 - pub fn run_alloc_peak(&self) -> usize { - self.gc_state.run_alloc_peak - } - - /// 本 run 累计分配字节:epoch arena + session 对象 arena + session 手工堆 - /// 账目(session 串 + session 对象及其属性向量 + GC 后补回的 BigInt)。 - /// 单次 run 内单调不减(执行期对象不回收、 - /// 串 GC 只降手工堆账目而 arena 不减);run 边界(reset)后重新起算, - /// 供单 run 分配上限判定。 - /// - /// 注意:手工堆账目只在 promote/字符串分配/GC 回收点更新,执行期对象 - /// 属性区(元素/属性向量扩容)增长对其不可见——上限判定须配合 - /// [`Self::run_alloc_bytes_full`] 的深采样层。 - pub(crate) fn run_alloc_bytes(&self) -> usize { - self.epoch.bump().allocated_bytes() - + self.gc_state.session_epoch.allocated_bytes() - + self.gc_state.session_bytes_allocated - } - - /// 本 run 累计分配字节的全量重算版:base 只取两个 arena 计数器 - /// (epoch + session 对象 arena),手工堆逐一重算——已登记对象的堆数据 - /// (属性/元素向量 + upvalue 列表 + native 状态盒)、session 串、BigInt - /// 与 upvalue cell 的容量。三分量(arena / 对象堆数据 / 串-BigInt-cell) - /// 两两不相交,且不含 session 手工堆账目——无交叠不双计,重算即属性区 - /// 扩容等账目盲区的兜底。 - pub(crate) fn run_alloc_bytes_full(&self) -> u64 { - let mut bytes = (self.epoch.bump().allocated_bytes() + self.gc_state.session_epoch.allocated_bytes()) as u64; - for &ptr in self - .gc_state - .epoch_object_ptrs - .iter() - .chain(self.gc_state.session_object_ptrs.iter()) - { - if ptr.is_null() { - continue; - } - // SAFETY: ptr 来自对象表登记,dispatch 安全点处仍有效。 - bytes += SessionGc::object_heap_data_bytes(unsafe { &*ptr }); - } - for &ptr in &self.gc_state.session_string_ptrs { - // SAFETY: ptr 来自字符串表登记,收尾前始终有效。 - bytes += (std::mem::size_of::() + unsafe { (*ptr).payload_bytes() }) as u64; - } - bytes += (self.gc_state.session_bigint_ptrs.borrow().len() * std::mem::size_of::()) as u64; - bytes += (self.gc_state.session_cell_ptrs.borrow().len() * std::mem::size_of::()) as u64; - bytes - } - - /// 无条件执行一次完整 session GC(mark + 移动式 sweep + 串/BigInt 清扫)。 - /// - /// # 副作用 - /// - 存活对象复制进新 session arena,全部根与原生盒按转发表重写; - /// `session_bytes_allocated` 重置为清扫后的存活字节。 - /// - /// # 注意事项 - /// - 须在执行外的安全点调用(无在途 builtin 局部裸指针、dispatch 未重入); - /// 执行期触发仍走水位路径,本入口供事后观测(如基准测 workload 后留存堆)。 - pub fn collect_session_gc(&mut self) { - let mut session_gc = std::mem::take(&mut self.gc_state.session_gc); - session_gc.collect(self); - self.gc_state.session_gc = session_gc; - } - - /// 当前 epoch 中已分配并跟踪的对象数量(未晋升到 session 的临时对象)。 - pub fn epoch_object_count(&self) -> usize { - self.gc_state.epoch_object_ptrs.len() - } - - /// inline cache 命中率(0.0~1.0),用于观测 IC 预热效果。 - pub fn ic_hit_rate(&self) -> f64 { - self.profiling.ic_hit_rate() - } - - /// 累计执行的指令数(profiling)。 - pub fn instruction_count(&self) -> u64 { - self.profiling.instruction_count - } - - /// 全局 symbol 注册表中已注册的 key 数量。 - pub fn symbol_registry_len(&self) -> usize { - self.symbols.registry_len() - } - - /// inline cache 命中次数。 - pub fn ic_hit_count(&self) -> u64 { - self.profiling.ic_hits.get() - } - - /// inline cache 未命中次数。 - pub fn ic_miss_count(&self) -> u64 { - self.profiling.ic_misses.get() - } - - pub(crate) fn checked_object_ptr( - &mut self, val: JsValue, error_msg: &str, - ) -> Result, String> { - if !val.is_object() { - self.raise_type_error(error_msg)?; - return Ok(None); - } - let ptr = (val.to_bits() & PTR_MASK) as *mut JsObject; - let addr = ptr as usize; - if ptr.is_null() || addr < 0x10000 || addr % std::mem::align_of::() != 0 { - self.raise_type_error(error_msg)?; - return Ok(None); - } - Ok(Some(ptr)) - } - - pub(crate) fn raise_error_kind(&mut self, kind: &'static str, msg: &str) -> Result<(), String> { - self.raise_js_error(JsError::new(js_error_kind(kind), msg)) - } - - pub(crate) fn raise_js_error(&mut self, err: JsError) -> Result<(), String> { - let kind = js_error_kind_name(err.kind); - vm_debug!("raise_js_error: {} \"{}\"", kind, err.message); - let error = oxide_builtins::error::create_kind_error(self, kind, &err.message); - self.exception_value = Some(error); - self.pending_error_kind = Some(kind); - self.unwind() - } - - pub(crate) fn raise_type_error(&mut self, msg: &str) -> Result<(), String> { - self.raise_error_kind("TypeError", msg) - } - - pub(crate) fn error_message_text(&self, kind: &str, msg: &str) -> String { - format_error_message(kind, msg) - } - /// 推进线性同余 RNG 一步(Math.random 用)。首次调用以系统时间纳秒播种。 pub fn step_rng(&mut self) { if self.math_rng_state == 0 { @@ -1110,475 +453,6 @@ impl Vm { Some(unsafe { (*val.as_string_ptr()).to_owned_string() }) } - pub(crate) fn thrown_error_kind(&self, val: JsValue) -> &'static str { - if !val.is_object() { - return "Error"; - } - let name_si = self.kernel_core.perm_interner().intern("name").0; - let obj = unsafe { &*val.as_js_object_ptr() }; - let Some(name_val) = self.resolve_property(obj, name_si) else { - return "Error"; - }; - let Some(name) = self.lookup_str(name_val) else { - return "Error"; - }; - match name.as_str() { - "TypeError" => "TypeError", - "ReferenceError" => "ReferenceError", - "RangeError" => "RangeError", - "SyntaxError" => "SyntaxError", - "URIError" => "URIError", - "EvalError" => "EvalError", - "Error" => "Error", - _ => "Error", - } - } - - /// 把 `JsValue` 转为属性键 si(`u32`)。 - /// - /// 非负小整数(含整值 double)直接编码到整数键区间,免 to_string + intern; - /// 字符串中形如数组下标的规范数字串(`"5"`)映射到同一整数键,保证 - /// `obj["5"]` 与 `obj[5]` 键等价。 - /// - /// # 边界与前提 - /// - 索引超 `INT_KEY_COUNT`(2^30)时回退字符串键路径(intern + 反查仍正确) - /// - 负数与小数不进入整数键区间(`arr[-1]`/`arr[1.5]` 是普通字符串键) - pub(crate) fn property_key_si(&mut self, val: JsValue) -> Result { - if val.is_int() { - let i = val.as_int(); - if i >= 0 && (i as u32) < INT_KEY_COUNT { - return Ok(make_int_key(i as u32)); - } - } else if val.is_double() { - let d = val.as_double(); - if d >= 0.0 && d.fract() == 0.0 && d < INT_KEY_COUNT as f64 { - return Ok(make_int_key(d as u32)); - } - } else if val.is_string() { - // SAFETY: val 是字符串值,按载荷形态桥接为永久 key id。 - let s = unsafe { &*val.as_string_ptr() }; - if s.is_flat() { - // Flat:良形 UTF-8 文本,走与单元路径同口径的编码入键空间。 - return Ok(self.string_key_text(s.as_str())); - } - // 单元载荷:键推导同规范(见 string_key_units)。 - return Ok(self.string_key_units(&s.units())); - } - // Symbol 值直接编码为 Symbol 键(不进字符串 interner,键相互独立)。 - if val.is_symbol() { - return Ok(make_symbol_key(val.as_symbol_index())); - } - // well-known symbol 是空对象:按指针比对映射到各自的 well-known Symbol 键, - // 避免全部塌缩成同一个键。 - if val.is_object() { - if let Some(id) = oxide_runtime_api::well_known_symbol_id(self, val.as_js_object_ptr()) { - return Ok(make_well_known_symbol_key(id)); - } - // ToPropertyKey:对象经 ToPrimitive(string hint),结果为 Symbol 时直接作键; - // 其余字符串按单元序列推导键(避免 lossy 文本桥接破坏孤立 surrogate 键)。 - let prim = coercion::to_primitive(val, coercion::ToPrimitiveHint::String, self)?; - if prim.is_symbol() { - return Ok(make_symbol_key(prim.as_symbol_index())); - } - let units = oxide_runtime_api::to_units_full(prim, self)?; - return Ok(self.string_key_units(&units)); - } - // 其它原始值(BigInt 等):ToPropertyKey 一律转字符串并走规范化,避免与 - // 数字键区间分裂(`o[5n]` 与 `o["5"]`/`o[5]` 必须同键)。 - let units = oxide_runtime_api::to_units_full(val, self)?; - Ok(self.string_key_units(&units)) - } - - /// 从良形文本串推导属性键 si(`property_key_si` Flat 分支与 `string_key_si` - /// 条目的共享入口):规范数组下标 → 整数键,其余以 `encode_key` 形态入键空间 - /// ——无 FFFD 的良形文本编码恒等,含 FFFD 的键与单元路径(Cons 载荷、 - /// `string_key_si`)收敛到同一编码形态,同一逻辑键不因构造路径分裂。 - pub(crate) fn string_key_text(&self, text: &str) -> u32 { - if let Some(i) = canonical_index_of(text) { - return make_int_key(i); - } - if text.contains('\u{FFFD}') { - let units: Vec = text.encode_utf16().collect(); - let key = oxide_kernel::string_forge::encode_key(&units); - self.kernel_core.perm_interner().intern(&key).0 - } else { - self.kernel_core.perm_interner().intern(text).0 - } - } - - /// 从单元序列推导属性键 si(`property_key_si` 字符串分支的口径抽取): - /// 规范数组下标 → 整数键,其余以 `encode_key` 形态入键空间(孤立 surrogate - /// 以转义形态区分,不与良形键碰撞)。 - pub(crate) fn string_key_units(&self, units: &[u16]) -> u32 { - if let Some(i) = canonical_index_units(units) { - make_int_key(i) - } else { - let key = oxide_kernel::string_forge::encode_key(units); - self.kernel_core.perm_interner().intern(&key).0 - } - } - - pub(crate) fn array_index_from_property_key(&self, prop_name_si: u32) -> Option { - if is_int_key(prop_name_si) { - return Some(int_key_value(prop_name_si)); - } - let key = self.kernel_core.perm_interner().lookup(prop_name_si)?; - if key.is_empty() || (key.len() > 1 && key.starts_with('0')) { - return None; - } - key.parse::().ok() - } - - /// 删除属性引用基非对象面(delete 成员表达式的 ToObject 基求值): - /// null/undefined 基 ToObject 抛 TypeError(规范 12.5.3.2 步 5.b);其余原始 - /// 基装箱后按自身属性集判删除成败——字符串装箱体自身属性恰为规范下标 - /// 0..len-1 与 "length"(均不可配置,删除失败返回 false),其它键与其它 - /// 原始装箱体(number/boolean/BigInt/Symbol 无自身属性)删除成功返回 - /// true。 - /// - /// # 步骤 - /// 1. null/undefined 基抛 TypeError(调用方经 `?` 传播异常) - /// 2. 字符串基按下标键区间判自身属性;非字符串基恒真 - /// 3. 布尔结果写 `rd` 槽(与对象基臂同槽位) - /// - /// # 边界与前提 - /// - 调用点已证基非对象;对象基走既有对象臂,不进本函数 - /// - 字符串长度口径按 UTF-16 码元(与字符串自身属性面一致);非规范数字串 - /// 键(前导零等)与 symbol 键走"无自身属性"面 - pub(crate) fn delete_prop_non_object_base( - &mut self, base: JsValue, rd: usize, key_si: u32, - ) -> Result { - if base.is_null() || base.is_undefined() { - self.raise_error_kind("TypeError", "delete on non-object")?; - } - let deleted = if base.is_string() { - let len = unsafe { (*base.as_string_ptr()).units().len() }; - match self.array_index_from_property_key(key_si) { - Some(i) if (i as usize) < len => false, - Some(_) => true, - None => key_si != self.length_si, - } - } else { - true - }; - self.regs[rd] = JsValue::bool(deleted); - Ok(false) - } - - pub(crate) fn resolve_property(&self, obj: &JsObject, prop_name_si: u32) -> Option { - vm_trace!("resolve_property: shape_id={} prop_name_si={}", obj.shape_id(), prop_name_si); - let length_si = self.length_si; - if obj.is_array() && prop_name_si == length_si { - return Some(obj.logical_len_value()); - } - if obj.is_array() { - if let Some(index) = self.array_index_from_property_key(prop_name_si) { - // 数组元素区:hole(删除标记)视为不存在。 - if index < obj.array_prop_count && !obj.prop_meta_at(index).is_some_and(|m| m.is_hole()) { - return Some(obj.get_prop_at(index)); - } - } - } - if let Some(pos) = self.kernel_core.shape_forge().lookup_position(obj.shape_id(), prop_name_si) { - let val = obj.get_prop_at(pos); - if !val.is_undefined() || obj.prop_vec_len() > pos as usize { - return Some(val); - } - } - let mut proto = obj.proto(); - let mut depth = 0usize; - while proto.is_object() && depth < MAX_PROTO_CHAIN_DEPTH { - depth += 1; - let proto_obj = unsafe { &*proto.as_js_object_ptr() }; - if let Some(pos) = self - .kernel_core - .shape_forge() - .lookup_position(proto_obj.shape_id(), prop_name_si) - { - let val = proto_obj.get_prop_at(pos); - if !val.is_undefined() || proto_obj.prop_vec_len() > pos as usize { - return Some(val); - } - } - proto = proto_obj.proto(); - } - None - } - - pub(crate) fn get_own_property_slot(&self, obj: &JsObject, prop_name_si: u32) -> Option { - let length_si = self.length_si; - if obj.is_array() && prop_name_si == length_si { - return None; - } - if obj.is_array() { - if let Some(index) = self.array_index_from_property_key(prop_name_si) { - // 数组元素区:hole(删除标记)视为不存在。 - if index < obj.array_prop_count && !obj.prop_meta_at(index).is_some_and(|m| m.is_hole()) { - return Some(index); - } - } - } - self.kernel_core - .shape_forge() - .lookup_position(obj.shape_id(), prop_name_si) - .and_then(|pos| { - if obj.is_array() { - // 数组属性存储索引 = array_prop_count + shape 槽位(与元素区分)。 - let idx = obj.array_prop_count as usize + pos as usize; - let val = obj.get_prop_at(idx); - if !val.is_undefined() || obj.prop_vec_len() > idx { - Some(idx as u32) - } else { - None - } - } else { - let val = obj.get_prop_at(pos); - if !val.is_undefined() || obj.prop_vec_len() > pos as usize { - Some(pos) - } else { - None - } - } - }) - } - - pub(crate) fn call_function_sync( - &mut self, callee: JsValue, receiver: JsValue, args: &[JsValue], - ) -> Result { - vm_debug!("call_function_sync: args={} callee_is_object={}", args.len(), callee.is_object()); - if !callee.is_object() { - return Err(self.error_message_text("TypeError", "accessor is not callable")); - } - let callee_obj = unsafe { &*callee.as_js_object_ptr() }; - if !callee_obj.is_function() { - return Err(self.error_message_text("TypeError", "accessor is not callable")); - } - - if let Some(native_fn) = callee_obj.native_fn() { - 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()); - } - // 大实参集(超过寄存器窗口):窗口外的实参转存 spill 栈溢出区(GC 根), - // native 侧经 native_arg_count/native_arg_at 读取;窗口内仍按寄存器 - // 协议打包,保证未迁移的 builtin 行为不变。 - let overflow_base = self.spill_stack.len(); - let saved_overflow_base = self.native_overflow_base; - let saved_overflow_count = self.native_overflow_count; - if args.len() > Self::SYNC_NATIVE_ARG_LIMIT { - self.spill_stack.extend_from_slice(&args[Self::SYNC_NATIVE_ARG_LIMIT..]); - self.native_overflow_base = overflow_base; - self.native_overflow_count = args.len() - Self::SYNC_NATIVE_ARG_LIMIT; - } - // native 回调只写 regs[0..args.len()] 实参区 + regs[253]/[254](receiver/callee), - // 窗口 = 调用方活动寄存器 ∪ 实参写入区;窗口外槽回调不触碰,无需保存。 - let window = (self.active_reg_limit as usize).max(args.len() + 3).min(253); - let mut saved_window = self.inline_reg_pool.take().unwrap_or_default(); - saved_window.clear(); - saved_window.extend_from_slice(&self.regs[..window]); - let saved_r253 = self.regs[253]; - let saved_r254 = self.regs[254]; - let pack_limit = args.len().min(Self::SYNC_NATIVE_ARG_LIMIT); - let arg_regs = self.pack_sync_native_call_args(receiver, callee, &args[..pack_limit]); - // SAFETY: native_fn 经 set_native_fn 以合法 NativeFn 指针设置; - // native_fn_ptr_to_fn 是 NativeFnPtr → NativeFn 的唯一强制转换点。 - let func: NativeFn = unsafe { native_fn_ptr_to_fn(native_fn) }; - self.native_call_depth += 1; - let result = func(self, &arg_regs); - self.native_call_depth -= 1; - // 溢出区随本次调用结束截断回收,并还原外层的溢出区描述(嵌套调用安全)。 - if args.len() > Self::SYNC_NATIVE_ARG_LIMIT { - self.spill_stack.truncate(overflow_base); - } - self.native_overflow_base = saved_overflow_base; - self.native_overflow_count = saved_overflow_count; - // 窗口回拷 + regs[253]/[254] 单回,缓冲归还池复用。 - self.regs[..window].copy_from_slice(&saved_window); - self.regs[253] = saved_r253; - self.regs[254] = saved_r254; - self.inline_reg_pool = Some(saved_window); - return match result { - NativeResult::Ok(val) => Ok(val), - NativeResult::Err(err) => { - self.last_uncaught_value = Some(err); - Err(self.error_text(err)) - } - NativeResult::TailCall { callee, this, args } => self.call_function_sync(callee, this, &args), - }; - } - - // 字节码函数在自身(同一 epoch)内联执行,防止 use-after-free。 - // 独立子 VM 拥有不同 epoch:返回含子 VM epoch 指针的 JsValue 后再销毁子 VM, - // 会在 release 构建中产生悬垂指针 / 访问违规。 - self.call_bytecode_function_inline(callee, callee_obj, receiver, args) - } - - /// 压帧窗口上界:调用方活动寄存器与调用点存活上界取 min。 - /// - /// # 边界与前提 - /// - `call_window` 为 CALL ext 高 8 位编码的存活上界;0 = 未编码(回退全量)。 - /// - 窗口至少为 1(reg 0 恒为调用结果槽)。 - pub(crate) fn call_window_limit(&self, caller_active_reg_limit: u8, call_window: u8) -> u8 { - if call_window == 0 { - caller_active_reg_limit - } else { - caller_active_reg_limit.min(call_window) - } - .max(1) - } - - /// 当前执行上下文的严格模式标志:属性写失败路径据此分派(严格抛 - /// TypeError,sloppy 静默 no-op)。 - /// - /// # 边界与前提 - /// - inline 执行期间帧表可能非空(调用方帧隔离在外),须以 - /// `inline_frames_base` 为基线区分"inline 前已有帧"与"inline 内新压帧" - /// (CALL/accessor 帧严格性归目标函数,最内层执行上下文即帧栈顶)。 - /// - 无帧且无 inline 时为顶层脚本执行,取 `top_level_strict`。 - pub(crate) fn current_strict(&self) -> bool { - if self.inline_callee.is_some() { - if self.frames.len() > self.inline_frames_base { - return self.frames.last().unwrap().strict; - } - return self.inline_strict; - } - if let Some(frame) = self.frames.last() { - return frame.strict; - } - self.top_level_strict - } - - #[expect(clippy::too_many_arguments)] - pub(crate) fn push_bytecode_frame( - &mut self, callee: JsValue, this_value: JsValue, args: FrameArgs, construct_result_reg: Option, - constructed_this: Option, new_target: JsValue, continuation: FrameContinuation, call_window: u8, - ) -> Result<(), String> { - vm_trace!( - "push_bytecode_frame: depth={}, args={}, continuation={:?}", - self.frames.len(), - args.len(), - continuation - ); - if !callee.is_object() { - return Err(self.error_message_text("TypeError", "CALL target is not callable")); - } - let obj = unsafe { &*callee.as_js_object_ptr() }; - if !obj.is_function() || obj.sub_module_index() == 0 { - return Err(self.error_message_text("TypeError", "CALL target is not callable")); - } - let sub_idx = obj.sub_module_index() as usize; - let gen = obj.table_gen(); - let table = match self.tables.get(&gen) { - Some(t) => t, - None => return Err(format!("CALL: module table gen {} not available", gen)), - }; - if sub_idx >= table.modules.len() { - return Err(format!("CALL: sub_module_index {} out of bounds (max {})", sub_idx, table.modules.len())); - } - if self.frames.len() >= self.kernel_core.config.max_call_depth { - return self.raise_error_kind("RangeError", "Maximum call stack size exceeded"); - } - - // 被调模块取 Arc 克隆(与调用方共享同一编译产物):后续 activate_immutables - // 等 &mut self 调用期间不持有对注册表的借用。 - let sub = Arc::clone(&table.modules[sub_idx]); - let sub_bytecode = Arc::clone(&sub.bytecode); - let sub_n_args = sub.n_args as usize; - let sub_n_registers = sub.n_registers; - let sub_param_base = sub.param_base as usize; - let sub_is_arrow = sub.is_arrow; - let sub_is_strict = sub.is_strict; - // 窗口 = min(调用方活动寄存器, 存活上界);call_window=0 表示调用方全量 - // (运行时发起路径 / 未编码的旧模块)。恢复按窗口回拷,active_reg_limit - // 仍还原为调用方真实值(caller_active_reg_limit)。 - let caller_active_reg_limit = self.active_reg_limit.max(1); - let caller_reg_limit = self.call_window_limit(caller_active_reg_limit, call_window); - let saved_reg_offset = self.save_stack.len() as u32; - self.save_stack.extend_from_slice(&self.regs[..caller_reg_limit as usize]); - let saved_this = self.regs[254]; - let saved_new_target = self.regs[255]; - - // 完整实参先写入 spill 栈实参区(在帧的 spill 区之前):CREATE_ARGUMENTS 据此 - // 构建 arguments 对象,帧恢复时随 spill 区截断一起丢弃。spill 在前、形参在后, - // 且形参源改读 spill 区——实参源区间与形参写入区在共享寄存器文件内重叠时 - // 不会先写后读串值(nested callee identity 高位 param_base 可落入实参区间)。 - let args_base = self.spill_stack.len() as u32; - match args { - FrameArgs::Slice(s) => self.spill_stack.extend_from_slice(s), - FrameArgs::RegRange { first, count } => { - for i in 0..count { - self.spill_stack.push(self.regs[first.wrapping_add(i as u8) as usize]); - } - } - } - let args_count = args.len().min(u16::MAX as usize) as u16; - - // 形参拷贝:源为 spill 实参区(与调用方寄存器隔离),实参不足补 undefined。 - for i in 0..sub_n_args { - let v = if i < args_count as usize { - self.spill_stack[args_base as usize + i] - } else { - JsValue::undefined() - }; - self.regs[sub_param_base + i] = v; - } - // this 绑定:箭头函数恒用词法捕获;sloppy 普通函数 this 为 null/undefined 时 - // 替换为全局对象(ECMA-262 10.4.3);严格模式与显式方法/构造 this 原样保留。 - self.regs[254] = if sub_is_arrow { - obj.captured_this() - } else if !sub_is_strict && this_value.is_nullish() { - JsValue::from_js_object(self.session.global_object().as_ptr() as *mut JsObject) - } else { - this_value - }; - self.regs[255] = new_target; - - self.saved_bytecode_stack.push(std::mem::take(&mut self.bytecode)); - self.saved_immutables_stack.push(self.active_immutables); - // 记录调用方 flat_id 与表代际,进入被调模块(标签模板 site 缓存按 - // (代际, flat_id) 隔离)。 - self.saved_flat_id_stack.push(self.active_flat_id); - self.saved_table_gen_stack.push(self.active_table_gen); - self.active_flat_id = sub_idx as u32; - self.active_table_gen = gen; - - let function_name = sub - .function_name - .as_deref() - .map(|name| self.kernel_core.perm_interner().intern(name).0) - .unwrap_or(0); - - self.frames.push(CallFrame { - return_addr: self.pc, - function_name, - caller_reg_limit, - caller_active_reg_limit, - saved_reg_offset, - spill_offset: self.spill_stack.len() as u32, - arguments_base: args_base, - arguments_count: args_count, - saved_this, - saved_new_target, - callee, - construct_result_reg, - constructed_this, - is_derived_constructor: obj.is_derived_constructor(), - super_called: false, - strict: sub_is_strict, - continuation, - }); - - self.pc = 0; - self.bytecode = sub_bytecode; - self.activate_immutables(gen, sub_idx, &sub.constants); - self.cell_stack.push(Vec::with_capacity(sub.cells_needed as usize)); - self.reload_builtin_mirror_slots(&sub.builtin_reg_map); - - self.active_reg_limit = sub_n_registers.max(1); - self.pc = 0; - Ok(()) - } - pub(crate) fn dispatch(&mut self) -> Result { // config.max_steps / max_alloc_bytes 逐指令只读且循环内不变:提到循环外, // 免每次经 kernel_core Arc 指针追寻读取(热点内仅有的 config 访问)。 diff --git a/crates/oxide_vm/src/vm/props.rs b/crates/oxide_vm/src/vm/props.rs new file mode 100644 index 00000000..77fb9cc3 --- /dev/null +++ b/crates/oxide_vm/src/vm/props.rs @@ -0,0 +1,223 @@ +//! 属性键推导与解析:JsValue 转属性键 si(整数键区间/字符串键/Symbol 键)、 +//! 数组下标反解、非对象基删除与属性解析及自身属性槽查找。 + +use oxide_runtime_api as coercion; + +use oxide_types::object::JsObject; +use oxide_types::private_key::{ + int_key_value, is_int_key, make_int_key, make_symbol_key, make_well_known_symbol_key, INT_KEY_COUNT, +}; +use oxide_types::value::JsValue; + +use super::{canonical_index_of, canonical_index_units, Vm, MAX_PROTO_CHAIN_DEPTH}; +use crate::vm_trace; + +impl Vm { + /// 把 `JsValue` 转为属性键 si(`u32`)。 + /// + /// 非负小整数(含整值 double)直接编码到整数键区间,免 to_string + intern; + /// 字符串中形如数组下标的规范数字串(`"5"`)映射到同一整数键,保证 + /// `obj["5"]` 与 `obj[5]` 键等价。 + /// + /// # 边界与前提 + /// - 索引超 `INT_KEY_COUNT`(2^30)时回退字符串键路径(intern + 反查仍正确) + /// - 负数与小数不进入整数键区间(`arr[-1]`/`arr[1.5]` 是普通字符串键) + pub(crate) fn property_key_si(&mut self, val: JsValue) -> Result { + if val.is_int() { + let i = val.as_int(); + if i >= 0 && (i as u32) < INT_KEY_COUNT { + return Ok(make_int_key(i as u32)); + } + } else if val.is_double() { + let d = val.as_double(); + if d >= 0.0 && d.fract() == 0.0 && d < INT_KEY_COUNT as f64 { + return Ok(make_int_key(d as u32)); + } + } else if val.is_string() { + // SAFETY: val 是字符串值,按载荷形态桥接为永久 key id。 + let s = unsafe { &*val.as_string_ptr() }; + if s.is_flat() { + // Flat:良形 UTF-8 文本,走与单元路径同口径的编码入键空间。 + return Ok(self.string_key_text(s.as_str())); + } + // 单元载荷:键推导同规范(见 string_key_units)。 + return Ok(self.string_key_units(&s.units())); + } + // Symbol 值直接编码为 Symbol 键(不进字符串 interner,键相互独立)。 + if val.is_symbol() { + return Ok(make_symbol_key(val.as_symbol_index())); + } + // well-known symbol 是空对象:按指针比对映射到各自的 well-known Symbol 键, + // 避免全部塌缩成同一个键。 + if val.is_object() { + if let Some(id) = oxide_runtime_api::well_known_symbol_id(self, val.as_js_object_ptr()) { + return Ok(make_well_known_symbol_key(id)); + } + // ToPropertyKey:对象经 ToPrimitive(string hint),结果为 Symbol 时直接作键; + // 其余字符串按单元序列推导键(避免 lossy 文本桥接破坏孤立 surrogate 键)。 + let prim = coercion::to_primitive(val, coercion::ToPrimitiveHint::String, self)?; + if prim.is_symbol() { + return Ok(make_symbol_key(prim.as_symbol_index())); + } + let units = oxide_runtime_api::to_units_full(prim, self)?; + return Ok(self.string_key_units(&units)); + } + // 其它原始值(BigInt 等):ToPropertyKey 一律转字符串并走规范化,避免与 + // 数字键区间分裂(`o[5n]` 与 `o["5"]`/`o[5]` 必须同键)。 + let units = oxide_runtime_api::to_units_full(val, self)?; + Ok(self.string_key_units(&units)) + } + + /// 从良形文本串推导属性键 si(`property_key_si` Flat 分支与 `string_key_si` + /// 条目的共享入口):规范数组下标 → 整数键,其余以 `encode_key` 形态入键空间 + /// ——无 FFFD 的良形文本编码恒等,含 FFFD 的键与单元路径(Cons 载荷、 + /// `string_key_si`)收敛到同一编码形态,同一逻辑键不因构造路径分裂。 + pub(crate) fn string_key_text(&self, text: &str) -> u32 { + if let Some(i) = canonical_index_of(text) { + return make_int_key(i); + } + if text.contains('\u{FFFD}') { + let units: Vec = text.encode_utf16().collect(); + let key = oxide_kernel::string_forge::encode_key(&units); + self.kernel_core.perm_interner().intern(&key).0 + } else { + self.kernel_core.perm_interner().intern(text).0 + } + } + + /// 从单元序列推导属性键 si(`property_key_si` 字符串分支的口径抽取): + /// 规范数组下标 → 整数键,其余以 `encode_key` 形态入键空间(孤立 surrogate + /// 以转义形态区分,不与良形键碰撞)。 + pub(crate) fn string_key_units(&self, units: &[u16]) -> u32 { + if let Some(i) = canonical_index_units(units) { + make_int_key(i) + } else { + let key = oxide_kernel::string_forge::encode_key(units); + self.kernel_core.perm_interner().intern(&key).0 + } + } + + pub(crate) fn array_index_from_property_key(&self, prop_name_si: u32) -> Option { + if is_int_key(prop_name_si) { + return Some(int_key_value(prop_name_si)); + } + let key = self.kernel_core.perm_interner().lookup(prop_name_si)?; + if key.is_empty() || (key.len() > 1 && key.starts_with('0')) { + return None; + } + key.parse::().ok() + } + + /// 删除属性引用基非对象面(delete 成员表达式的 ToObject 基求值): + /// null/undefined 基 ToObject 抛 TypeError(规范 12.5.3.2 步 5.b);其余原始 + /// 基装箱后按自身属性集判删除成败——字符串装箱体自身属性恰为规范下标 + /// 0..len-1 与 "length"(均不可配置,删除失败返回 false),其它键与其它 + /// 原始装箱体(number/boolean/BigInt/Symbol 无自身属性)删除成功返回 + /// true。 + /// + /// # 步骤 + /// 1. null/undefined 基抛 TypeError(调用方经 `?` 传播异常) + /// 2. 字符串基按下标键区间判自身属性;非字符串基恒真 + /// 3. 布尔结果写 `rd` 槽(与对象基臂同槽位) + /// + /// # 边界与前提 + /// - 调用点已证基非对象;对象基走既有对象臂,不进本函数 + /// - 字符串长度口径按 UTF-16 码元(与字符串自身属性面一致);非规范数字串 + /// 键(前导零等)与 symbol 键走"无自身属性"面 + pub(crate) fn delete_prop_non_object_base( + &mut self, base: JsValue, rd: usize, key_si: u32, + ) -> Result { + if base.is_null() || base.is_undefined() { + self.raise_error_kind("TypeError", "delete on non-object")?; + } + let deleted = if base.is_string() { + let len = unsafe { (*base.as_string_ptr()).units().len() }; + match self.array_index_from_property_key(key_si) { + Some(i) if (i as usize) < len => false, + Some(_) => true, + None => key_si != self.length_si, + } + } else { + true + }; + self.regs[rd] = JsValue::bool(deleted); + Ok(false) + } + + pub(crate) fn resolve_property(&self, obj: &JsObject, prop_name_si: u32) -> Option { + vm_trace!("resolve_property: shape_id={} prop_name_si={}", obj.shape_id(), prop_name_si); + let length_si = self.length_si; + if obj.is_array() && prop_name_si == length_si { + return Some(obj.logical_len_value()); + } + if obj.is_array() { + if let Some(index) = self.array_index_from_property_key(prop_name_si) { + // 数组元素区:hole(删除标记)视为不存在。 + if index < obj.array_prop_count && !obj.prop_meta_at(index).is_some_and(|m| m.is_hole()) { + return Some(obj.get_prop_at(index)); + } + } + } + if let Some(pos) = self.kernel_core.shape_forge().lookup_position(obj.shape_id(), prop_name_si) { + let val = obj.get_prop_at(pos); + if !val.is_undefined() || obj.prop_vec_len() > pos as usize { + return Some(val); + } + } + let mut proto = obj.proto(); + let mut depth = 0usize; + while proto.is_object() && depth < MAX_PROTO_CHAIN_DEPTH { + depth += 1; + let proto_obj = unsafe { &*proto.as_js_object_ptr() }; + if let Some(pos) = self + .kernel_core + .shape_forge() + .lookup_position(proto_obj.shape_id(), prop_name_si) + { + let val = proto_obj.get_prop_at(pos); + if !val.is_undefined() || proto_obj.prop_vec_len() > pos as usize { + return Some(val); + } + } + proto = proto_obj.proto(); + } + None + } + + pub(crate) fn get_own_property_slot(&self, obj: &JsObject, prop_name_si: u32) -> Option { + let length_si = self.length_si; + if obj.is_array() && prop_name_si == length_si { + return None; + } + if obj.is_array() { + if let Some(index) = self.array_index_from_property_key(prop_name_si) { + // 数组元素区:hole(删除标记)视为不存在。 + if index < obj.array_prop_count && !obj.prop_meta_at(index).is_some_and(|m| m.is_hole()) { + return Some(index); + } + } + } + self.kernel_core + .shape_forge() + .lookup_position(obj.shape_id(), prop_name_si) + .and_then(|pos| { + if obj.is_array() { + // 数组属性存储索引 = array_prop_count + shape 槽位(与元素区分)。 + let idx = obj.array_prop_count as usize + pos as usize; + let val = obj.get_prop_at(idx); + if !val.is_undefined() || obj.prop_vec_len() > idx { + Some(idx as u32) + } else { + None + } + } else { + let val = obj.get_prop_at(pos); + if !val.is_undefined() || obj.prop_vec_len() > pos as usize { + Some(pos) + } else { + None + } + } + }) + } +} diff --git a/crates/oxide_vm/src/vm/tables.rs b/crates/oxide_vm/src/vm/tables.rs index 837f8284..7e6cd02c 100644 --- a/crates/oxide_vm/src/vm/tables.rs +++ b/crates/oxide_vm/src/vm/tables.rs @@ -1,11 +1,15 @@ -//! 子模块平表代际:`TableGen`(模块平表 + 每模块不可变常量缓存),注册进 -//! `Vm::tables` 按代际管理。 +//! 子模块平表代际:`TableGen`(模块平表 + 每模块不可变常量缓存)及其表访问器, +//! 注册进 `Vm::tables` 按代际管理。 use std::sync::{Arc, OnceLock}; -use oxide_bytecode::module::CompiledModule; +use oxide_bytecode::module::{CompiledModule, Constant}; +use oxide_bytecode::opcode; +use oxide_types::object::JsObject; use oxide_types::value::JsValue; +use super::Vm; + /// 单代子模块平表:模块平表 + 每模块不可变常量缓存,注册进 `Vm::tables` 按代际管理。 /// /// 以 `Box` 承载(HashMap 扩容搬 Box 指针不搬被指物):`active_immutables` 与 @@ -22,3 +26,90 @@ pub(crate) struct TableGen { /// 不可变常量是标量 + perm 字符串,只读,GC 根收集按表代际遍历。 pub(crate) immutables: Vec>>, } + +impl Vm { + /// 活动模块已转换不可变常量的只读视图;任何 `run()` 之前为空。 + #[inline(always)] + pub(crate) fn immutables(&self) -> &[JsValue] { + if self.active_immutables.is_null() { + &[] + } else { + // SAFETY: active_immutables 指向某表代际 immutables 内 OnceLock 的常量 + // Vec,Vec 只填充一次且堆地址稳定;所指代际表在注册表内存活(当前 + // 代际恒在,旧代际由存活函数对象引用保活)。 + unsafe { &*self.active_immutables } + } + } + + /// 当前代际的子模块平表(可变,动态扩表与测试注入的目标)。 + pub(crate) fn current_table_mut(&mut self) -> &mut TableGen { + self.tables.get_mut(&self.current_gen).expect("当前代际表构造期已预登记") + } + + /// 当前执行帧所属代际的子模块平表:按帧切换时记录的 `active_table_gen` + /// 解析(压帧 / inline 入口 / run 顶层 / 挂起恢复置位,弹帧还原),跨 run + /// 调用时可异于 `current_gen`——帧内指令操作数是帧字节码所属代际平表里的 + /// flat_id,须按该代际解析。 + /// + /// # 边界与前提 + /// - 执行帧代际表恒在注册表中:顶层即当前代际(恒保留),帧 / inline 的 + /// 代际由存活 callee 函数对象保活,挂起恢复已按 callee 对象验证在位。 + pub(crate) fn active_table(&self) -> &TableGen { + self.tables.get(&self.active_table_gen).expect("执行帧的代际表须在注册表中") + } + + /// 函数对象 `sub_module_index` 指向的子模块条目:按对象自身记录的表代际 + /// 解析平表后按下标定位。 + /// + /// # 边界与前提 + /// - 原生函数哨兵 `sub_module_index() == 0` 不经本方法(调用方先行分派); + /// - 代际表已被回收或下标越界返回 None(调用方按各自越界口径报错)。 + pub(crate) fn callee_module<'a>(&'a self, obj: &'a JsObject) -> Option<&'a CompiledModule> { + self.tables + .get(&obj.table_gen()) + .and_then(|t| t.modules.get(obj.sub_module_index() as usize)) + .map(|m| m.as_ref()) + } + + /// 活动帧所属代际平表里 `active_flat_id` 指向的模块条目(镜像槽同步 / + /// 重载的名集载体)。 + /// + /// # 边界与前提 + /// - 活动代际表在执行期恒在注册表中(首 run 前为预登记的空表占位), + /// `active_flat_id` 越界返回 None(调用方按无模块处理)。 + pub(crate) fn active_module(&self) -> Option<&CompiledModule> { + self.active_table() + .modules + .get(self.active_flat_id as usize) + .map(|m| m.as_ref()) + } + + /// 子模块表注册表的当前代际条目总数(run 边界回收行为的测试钉)。 + pub fn table_gen_count(&self) -> usize { + self.tables.len() + } + + /// 激活代际 `gen` 平表中模块 `cache_idx` 的不可变常量,只转换一次存入该代际 + /// `immutables[cache_idx]`,并把 `active_immutables` 指向该 Vec。 + /// `constants` 由调用方传入(它已持有 `&module.constants`)。 + /// + /// # 边界与前提 + /// - `gen` 的表须已在注册表中(当前代际由 run() 登记,旧代际由存活函数 + /// 对象引用保活);`cache_idx` 须小于该代际平表长度。 + pub(crate) fn activate_immutables(&mut self, gen: u32, cache_idx: usize, constants: &[Constant]) { + // 用裸指针访问缓存槽,避免 get_or_init(借用表 immutables)与 &self 的 + // convert_immutables 闭包和随后对 active_immutables 的写入发生借用冲突。 + // 成立前提:代际表归注册表所有、只读、Box 承载地址稳定。 + let table = self.tables.get_mut(&gen).expect("激活的代际表须在注册表中"); + let slot: *const OnceLock> = &table.immutables[cache_idx]; + let vec = unsafe { &*slot }.get_or_init(|| self.convert_immutables(constants)); + self.active_immutables = vec.as_slice() as *const [JsValue]; + } + + /// 当前活动字节码的可变访问入口。bytecode 以 `Arc<[Instr]>` 与代际表 modules 源共享, + /// IC 写回经 `Arc::make_mut` 保证独占:独占时零拷贝原地写,共享时先深拷贝再写 + /// (IC miss 才触发,频率低)。所有写操作必须经此方法,防止共享缓冲被多实例污染。 + pub(crate) fn bytecode_mut(&mut self) -> &mut [opcode::Instr] { + Arc::make_mut(&mut self.bytecode) + } +}