mirror of
https://github.com/pjh456/OxideJS.git
synced 2026-10-23 18:21:13 +08:00
refactor: VM 主实现非派发段按职责拆入 vm 子模块
This commit is contained in:
@@ -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<u8> {
|
||||
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<JsValue, String> {
|
||||
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<u8>,
|
||||
constructed_this: Option<JsValue>, 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(())
|
||||
}
|
||||
}
|
||||
@@ -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<JsValue, String> {
|
||||
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<JsValue, String> {
|
||||
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<f64, String> {
|
||||
let primitive = self.coerce_primitive_bounded(value, false)?;
|
||||
Ok(coercion::to_number(primitive))
|
||||
}
|
||||
|
||||
pub(crate) fn coerce_int32_bounded(&mut self, value: JsValue) -> Result<i32, String> {
|
||||
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<u32, String> {
|
||||
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<Option<*mut JsObject>, 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::<JsObject>() != 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",
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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::<oxide_types::object::JsString>() + unsafe { (*ptr).payload_bytes() }) as u64;
|
||||
}
|
||||
bytes += (self.gc_state.session_bigint_ptrs.borrow().len() * std::mem::size_of::<num_bigint::BigInt>()) as u64;
|
||||
bytes += (self.gc_state.session_cell_ptrs.borrow().len() * std::mem::size_of::<Cell>()) 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()
|
||||
}
|
||||
}
|
||||
+8
-1134
File diff suppressed because it is too large
Load Diff
@@ -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<u32, String> {
|
||||
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<u16> = 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<u32> {
|
||||
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::<u32>().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<bool, String> {
|
||||
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<JsValue> {
|
||||
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<u32> {
|
||||
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
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -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<OnceLock<Vec<JsValue>>>,
|
||||
}
|
||||
|
||||
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<Vec<JsValue>> = &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)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user