docs: 补充注释与格式化
This commit is contained in:
@@ -56,7 +56,7 @@ pub fn bind_array(core: &Arc<KernelCore>, session: &KernelSession, global: &mut
|
||||
core,
|
||||
&[("toString", oxide_builtins::array::array_to_string::<crate::vm::Vm> as *const (), 0)],
|
||||
);
|
||||
// Built-in prototype methods are non-enumerable (otherwise they leak into for-in).
|
||||
// 内置原型方法不可枚举(否则会泄漏进 for-in)。
|
||||
let si = core.perm_interner().intern("toString").0;
|
||||
if let Some(pos) = core.shape_forge().lookup_position(array_proto.shape_id(), si) {
|
||||
array_proto.set_data_meta(pos, PropAttributes::new(true, false, true));
|
||||
|
||||
@@ -16,7 +16,7 @@ fn bind_error_subtype_constructor(
|
||||
|
||||
let mut ctor = Box::new(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::from_js_object(function_proto_ptr)));
|
||||
ctor.set_function(true);
|
||||
// SAFETY: ctor_fn is a NativeFn fn-item pointer cast to *const () by callers.
|
||||
// SAFETY: ctor_fn 是调用方转成 *const () 的 NativeFn 函数项指针。
|
||||
ctor.set_native_fn(Some(unsafe { NativeFnPtr::from_raw(ctor_fn) }));
|
||||
ctor.set_native_arg_count(1);
|
||||
|
||||
@@ -121,7 +121,7 @@ pub fn bind_error(core: &Arc<KernelCore>, session: &KernelSession, global: &mut
|
||||
{
|
||||
let err_ctor_ptr = session.builtin_world().error_constructor.as_ptr() as *mut JsObject;
|
||||
let err_ctor = unsafe { &mut *err_ctor_ptr };
|
||||
// SAFETY: error_constructor is a NativeFn fn-item.
|
||||
// SAFETY: error_constructor 是 NativeFn 函数项。
|
||||
err_ctor.set_native_fn(Some(unsafe {
|
||||
NativeFnPtr::from_raw(oxide_builtins::error::error_constructor::<crate::vm::Vm> as *const ())
|
||||
}));
|
||||
|
||||
@@ -53,7 +53,7 @@ pub fn bind_object(core: &Arc<KernelCore>, session: &KernelSession, global: &mut
|
||||
("valueOf", oxide_builtins::object::object_proto_value_of::<crate::vm::Vm> as *const (), 0),
|
||||
],
|
||||
);
|
||||
// Built-in prototype methods are non-enumerable (otherwise they leak into for-in).
|
||||
// 内置原型方法不可枚举(否则会泄漏进 for-in)。
|
||||
for name in ["toString", "valueOf"] {
|
||||
let si = core.perm_interner().intern(name).0;
|
||||
if let Some(pos) = core.shape_forge().lookup_position(object_proto.shape_id(), si) {
|
||||
|
||||
@@ -83,7 +83,7 @@ macro_rules! bind_constructor {
|
||||
}
|
||||
|
||||
pub(crate) fn configure_native_constructor(ctor: &mut JsObject, native_fn: *const (), arg_count: u8) {
|
||||
// SAFETY: native_fn is always a valid NativeFn fn-item pointer cast to *const () by callers.
|
||||
// SAFETY: native_fn 始终是调用方转成 *const () 的合法 NativeFn 函数项指针。
|
||||
ctor.set_native_fn(Some(unsafe { NativeFnPtr::from_raw(native_fn) }));
|
||||
ctor.set_native_arg_count(arg_count);
|
||||
}
|
||||
@@ -100,7 +100,7 @@ pub(crate) fn apply_binding_table(
|
||||
let shape_forge = core.shape_forge().as_ref();
|
||||
let string_forge = core.perm_interner().as_ref();
|
||||
for (name, func, nargs) in bindings {
|
||||
// SAFETY: all entries in the binding table are NativeFn fn-item pointers cast to *const ().
|
||||
// SAFETY: 绑定表中所有条目都是转成 *const () 的 NativeFn 函数项指针。
|
||||
let fn_ptr = unsafe { oxide_types::object::NativeFnPtr::from_raw(*func) };
|
||||
let _ = world.bind_method(target, shape_forge, string_forge, name, fn_ptr, *nargs);
|
||||
}
|
||||
@@ -491,9 +491,11 @@ pub fn bind_global_builtin_slots(core: &Arc<KernelCore>, session: &KernelSession
|
||||
bind_existing_global(core, global, "globalThis", global_this);
|
||||
}
|
||||
|
||||
/// Maintenance: when adding a `BuiltinDirtySet` group, update this rebind map,
|
||||
/// `BuiltinSnapshot`, and `BuiltinWorld::rebuild_with_dirty()` together.
|
||||
/// 按脏标记重绑被污染的内置对象;`dirty` 为 `None` 时绑定全部(初始化路径)。
|
||||
///
|
||||
/// # 注意事项
|
||||
/// 维护:新增 `BuiltinDirtySet` 分组时,须同步更新本重绑映射、`BuiltinSnapshot` 与
|
||||
/// `BuiltinWorld::rebuild_with_dirty()`。
|
||||
pub fn rebind_dirty_builtins(core: &Arc<KernelCore>, session: &mut KernelSession, dirty: Option<&BuiltinDirtySet>) {
|
||||
let global_ptr = session.global_object().as_ptr() as *mut JsObject;
|
||||
let global = unsafe { &mut *global_ptr };
|
||||
|
||||
@@ -33,14 +33,14 @@ impl Vm {
|
||||
let args_slice = &args_buf[..len];
|
||||
builtins_debug!("native_call depth={} args={}", self.native_call_depth, arg_count);
|
||||
|
||||
// SAFETY: native_fn was set via set_native_fn with a valid NativeFn pointer;
|
||||
// native_fn_ptr_to_fn is the single coercion point for NativeFnPtr → NativeFn.
|
||||
// SAFETY: native_fn 经 set_native_fn 以合法 NativeFn 指针设置;
|
||||
// native_fn_ptr_to_fn 是 NativeFnPtr → NativeFn 的唯一强制转换点。
|
||||
let func: NativeFn = unsafe { native_fn_ptr_to_fn(obj.native_fn().unwrap()) };
|
||||
// regs[254] is the caller's `this` register and doubles as the "current callee"
|
||||
// slot that dispatcher builtins (Function.prototype.bind/call/apply) read. Native
|
||||
// calls share the flat register file with the caller, so snapshot the caller's
|
||||
// `this`, expose the callee for the native's duration, then restore it — otherwise
|
||||
// every native call leaves its own function object in the caller's `this` register.
|
||||
// regs[254] 是调用方的 `this` 寄存器,同时充当分发型 builtin
|
||||
// (Function.prototype.bind/call/apply)读取的"当前 callee"槽。native 调用与
|
||||
// 调用方共享扁平寄存器文件,因此先快照调用方 `this`,在 native 执行期间暴露
|
||||
// callee,随后恢复——否则每次 native 调用都会把自己的函数对象留在调用方的
|
||||
// `this` 寄存器里。
|
||||
let saved_this = self.regs[254];
|
||||
self.regs[254] = callee;
|
||||
self.native_call_depth += 1;
|
||||
@@ -58,7 +58,7 @@ impl Vm {
|
||||
(err_val, self.thrown_error_kind(err_val))
|
||||
} else {
|
||||
let msg = if err_val.is_string() {
|
||||
// SAFETY: err_val is a string value.
|
||||
// SAFETY: err_val 是字符串值。
|
||||
unsafe { (*err_val.as_string_ptr()).data.clone() }
|
||||
} else {
|
||||
format!("{err_val}")
|
||||
@@ -184,8 +184,8 @@ impl Vm {
|
||||
}
|
||||
}
|
||||
}
|
||||
// Cell not yet created (hoisting order: CREATE_CLOSURE before MAKE_CELL).
|
||||
// Create cell lazily with caller's register value.
|
||||
// cell 尚未创建(hoisting 顺序:CREATE_CLOSURE 先于 MAKE_CELL),
|
||||
// 用调用方寄存器值惰性创建 cell。
|
||||
self.lazy_create_upvalue_cell(rd, uv_idx)
|
||||
}
|
||||
|
||||
@@ -195,7 +195,7 @@ impl Vm {
|
||||
let obj = unsafe { &mut *callee.as_js_object_ptr() };
|
||||
let upvals = obj.upvalues_slice_mut();
|
||||
if uv_idx < upvals.len() {
|
||||
// Try to get value from caller's cell table (MAKE_CELL ran after CREATE_CLOSURE)
|
||||
// 尝试从调用方 cell 表取初值(MAKE_CELL 在 CREATE_CLOSURE 之后执行)。
|
||||
let val = if self.cell_stack.len() >= 2 {
|
||||
let caller_cells = &self.cell_stack[self.cell_stack.len() - 2];
|
||||
if uv_idx < caller_cells.len() && !caller_cells[uv_idx].is_null() {
|
||||
@@ -317,10 +317,10 @@ impl Vm {
|
||||
|
||||
if super_obj.native_fn().is_some() {
|
||||
self.regs[253] = derived_this;
|
||||
self.regs[254] = super_ctor; // bind_dispatcher reads regs[254] as the wrapper callee
|
||||
self.regs[254] = super_ctor; // bind_dispatcher 把 regs[254] 当作包装 callee 读取
|
||||
let (args_buf, len) = Self::build_native_args(first_arg_reg, arg_count, 253);
|
||||
// SAFETY: native_fn was set via set_native_fn with a valid NativeFn pointer;
|
||||
// native_fn_ptr_to_fn is the single coercion point for NativeFnPtr → NativeFn.
|
||||
// SAFETY: native_fn 经 set_native_fn 以合法 NativeFn 指针设置;
|
||||
// native_fn_ptr_to_fn 是 NativeFnPtr → NativeFn 的唯一强制转换点。
|
||||
let func: NativeFn = unsafe { native_fn_ptr_to_fn(super_obj.native_fn().unwrap()) };
|
||||
match func(self, &args_buf[..len]) {
|
||||
NativeResult::Ok(val) => {
|
||||
@@ -336,8 +336,8 @@ impl Vm {
|
||||
}
|
||||
}
|
||||
NativeResult::TailCall { callee, this, args } => {
|
||||
// e.g. bound function: resolve the tail call, use its return
|
||||
// value as the constructed instance (or fall back to derived_this).
|
||||
// 如 bound 函数:解析尾调用,用其返回值作为构造实例
|
||||
// (或回退到 derived_this)。
|
||||
match self.call_function_sync(callee, this, &args) {
|
||||
Ok(val) => {
|
||||
self.regs[254] = if val.is_object() { val } else { derived_this };
|
||||
|
||||
@@ -1 +1 @@
|
||||
// dispatch helper functions extracted from vm.rs
|
||||
//! 异常处理相关 dispatch 辅助函数(占位,逻辑位于 vm_dispatch_ctrl / vm_dispatch_misc)。
|
||||
|
||||
@@ -1 +1 @@
|
||||
// dispatch helper functions extracted from vm.rs
|
||||
//! for-in 相关 dispatch 辅助函数(占位,逻辑位于 vm_dispatch_misc)。
|
||||
|
||||
@@ -65,7 +65,7 @@ impl Vm {
|
||||
pub(crate) fn dispatch_store_var(&mut self, rd: usize, a: usize, b: usize) -> Result<bool, String> {
|
||||
vm_trace!("STORE_VAR r{}={:?} const={}", rd, self.regs[a], b);
|
||||
if b != 0 {
|
||||
// const guard: check if already initialized
|
||||
// const guard:检查目标槽是否已初始化。
|
||||
if !self.regs[rd].is_undefined() {
|
||||
self.raise_error_kind("TypeError", "Assignment to constant variable")?;
|
||||
return Ok(true);
|
||||
@@ -75,8 +75,13 @@ impl Vm {
|
||||
Ok(false)
|
||||
}
|
||||
|
||||
/// SPILL:regs[rd] → spill_stack[帧基址 + slot]。ext 字 = slot u16(D-06)。
|
||||
/// 帧基址 = 当前 CallFrame.spill_offset(顶层模块无帧 = 0,D-08)。
|
||||
/// SPILL:`regs[rd]` → `spill_stack[帧基址 + slot]`。
|
||||
///
|
||||
/// ext 字为 slot(u16);帧基址 = 当前 CallFrame.spill_offset(顶层模块无帧为 0)。
|
||||
/// # 边界与前提
|
||||
/// - slot 超 u16 范围返回错误(RegAlloc 保证发射在界内)。
|
||||
/// # 副作用
|
||||
/// - 写 spill 栈,可能触发 resize 扩容。
|
||||
#[inline(always)]
|
||||
pub(crate) fn dispatch_spill(&mut self, rd: usize) -> Result<(), String> {
|
||||
let slot = self.bytecode[self.pc] as usize;
|
||||
@@ -94,7 +99,11 @@ impl Vm {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// UNSPILL:spill_stack[帧基址 + slot] → regs[rd]。越界读 undefined(防御,D-06 slot 由 RegAlloc 保证在界内)。
|
||||
/// UNSPILL:`spill_stack[帧基址 + slot]` → `regs[rd]`。
|
||||
///
|
||||
/// # 边界与前提
|
||||
/// - slot 超 u16 范围返回错误。
|
||||
/// - 越界读返回 undefined(防御:slot 由 RegAlloc 保证在界内)。
|
||||
#[inline(always)]
|
||||
pub(crate) fn dispatch_unspill(&mut self, rd: usize) -> Result<(), String> {
|
||||
let slot = self.bytecode[self.pc] as usize;
|
||||
|
||||
@@ -46,7 +46,7 @@ impl Vm {
|
||||
if val.is_string() {
|
||||
let length_si = self.kernel_core.perm_interner().intern("length").0;
|
||||
if prop_name_si == length_si {
|
||||
// SAFETY: val is a string value.
|
||||
// SAFETY: val 是字符串值。
|
||||
let len = unsafe { (*val.as_string_ptr()).data.encode_utf16().count() };
|
||||
return Ok(Some(JsValue::int(len as i32)));
|
||||
}
|
||||
@@ -207,14 +207,14 @@ impl Vm {
|
||||
prop_cache_miss();
|
||||
ic_debug!("IC_GET miss shape={} prop={}", obj.shape_id(), prop_name_si);
|
||||
let resolved = self.ordinary_get(obj, prop_name_si, val)?;
|
||||
// Try own property first (fast path, depth=0).
|
||||
// 先试自身属性(快路径,depth=0)。
|
||||
if let Some(pos) = self.kernel_core.shape_forge().lookup_position(obj.shape_id(), prop_name_si) {
|
||||
if !obj.is_accessor_meta(pos) {
|
||||
ic_helper::write_ic_back(&mut self.bytecode, self.pc, obj.shape_id(), pos, 0);
|
||||
ic_debug!("IC_GET write-back own shape={} slot={}", obj.shape_id(), pos);
|
||||
}
|
||||
} else {
|
||||
// Walk proto chain to find which object actually owns this property.
|
||||
// 沿原型链查找真正拥有该属性的对象。
|
||||
let mut cursor = obj.proto().as_js_object_ptr();
|
||||
let mut depth = 1u8;
|
||||
while !cursor.is_null() {
|
||||
|
||||
@@ -1,22 +1,20 @@
|
||||
//! Inline-cache (IC) bytecode-stream helpers.
|
||||
//! inline cache(IC)字节码流辅助函数。
|
||||
//!
|
||||
//! Contains ALL IC read/write/clear logic — property dispatch and member
|
||||
//! update handlers route through here, so IC format changes (like the future
|
||||
//! side-table migration) require editing only this file.
|
||||
//! 汇聚全部 IC 读写/清零逻辑——属性分发与成员更新 handler 都经由此处,IC 格式
|
||||
//! 变更(如未来的 side-table 迁移)只需改这一个文件。
|
||||
//!
|
||||
//! IC entry format (3 extension words after opcode):
|
||||
//! ext0 = shape_id (24 bits)
|
||||
//! ext1 = slot_index (32 bits)
|
||||
//! ext2 = proto_depth (u8, 0 = own property on receiver)
|
||||
//! IC 项格式(操作码后 3 个扩展字):
|
||||
//! ext0 = shape_id(24 位)
|
||||
//! ext1 = slot_index(32 位)
|
||||
//! ext2 = proto_depth(u8,0 = 接收者自身属性)
|
||||
|
||||
use crate::vm_trace;
|
||||
use oxide_bytecode::opcode::{self, Instr};
|
||||
use oxide_types::object::JsObject;
|
||||
use oxide_types::value::JsValue;
|
||||
|
||||
/// Read the three IC extension words at `pc` (the bytes following an
|
||||
/// IC-bearing opcode), advance `pc` past them, and return the decoded
|
||||
/// `(shape_id, slot, proto_depth)`.
|
||||
/// 读取 `pc` 处 IC 指令后的三个扩展字,把 `pc` 前进越过它们,
|
||||
/// 返回解码的 `(shape_id, slot, proto_depth)`。
|
||||
pub(crate) fn read_ic_entry(bytecode: &[Instr], pc: &mut usize) -> (u32, u32, u8) {
|
||||
let ext0 = bytecode[*pc];
|
||||
let ext1 = bytecode[*pc + 1];
|
||||
@@ -25,9 +23,8 @@ pub(crate) fn read_ic_entry(bytecode: &[Instr], pc: &mut usize) -> (u32, u32, u8
|
||||
(ext0 & 0x00FF_FFFF, ext1, (ext2 & 0xFF) as u8)
|
||||
}
|
||||
|
||||
/// Write a resolved `(shape_id, slot, proto_depth)` back into the three IC
|
||||
/// extension words at `pc - 3` (they precede the current instruction).
|
||||
/// Called on an IC miss after resolving a property.
|
||||
/// 把解析出的 `(shape_id, slot, proto_depth)` 写回 `pc - 3` 处的三个 IC 扩展字
|
||||
/// (位于当前指令之前)。IC 未命中并完成属性解析后调用。
|
||||
pub(crate) fn write_ic_back(bytecode: &mut [Instr], pc: usize, shape_id: u32, slot_index: u32, proto_depth: u8) {
|
||||
debug_assert!(pc >= 3, "IC write-back requires 3 extension words before pc");
|
||||
vm_trace!("write_ic_back: pc={} shape_id={} slot={} depth={}", pc, shape_id, slot_index, proto_depth);
|
||||
@@ -36,7 +33,7 @@ pub(crate) fn write_ic_back(bytecode: &mut [Instr], pc: usize, shape_id: u32, sl
|
||||
bytecode[pc - 1] = proto_depth as u32;
|
||||
}
|
||||
|
||||
/// Zero every IC extension word in the stream, invalidating all cached shapes.
|
||||
/// 把流中所有 IC 扩展字清零,使全部缓存 shape 失效。
|
||||
pub(crate) fn clear_ic_caches(bytecode: &mut [Instr]) {
|
||||
let mut i = 0;
|
||||
while i < bytecode.len() {
|
||||
@@ -54,8 +51,7 @@ pub(crate) fn clear_ic_caches(bytecode: &mut [Instr]) {
|
||||
}
|
||||
}
|
||||
|
||||
/// Walk `proto_depth` steps up the prototype chain, returning a raw pointer
|
||||
/// to the target object (or null if the chain is shorter).
|
||||
/// 沿原型链上溯 `proto_depth` 步,返回目标对象的裸指针(链不足时返回 null)。
|
||||
#[inline(always)]
|
||||
fn resolve_proto_target_raw(obj: *const JsObject, proto_depth: u8) -> *const JsObject {
|
||||
if proto_depth == 0 {
|
||||
|
||||
@@ -244,8 +244,8 @@ mod tests {
|
||||
let first = plain_object(&mut vm);
|
||||
let promoted = vm.promote_object(first);
|
||||
assert!(!promoted.is_null());
|
||||
// The shared forwarding map must be cleared after each promote so a
|
||||
// later promote (or GC sweep) never observes stale old->new mappings.
|
||||
// 共享 forwarding 表必须在每次 promote 后清空,使后续 promote(或 GC 清扫)
|
||||
// 永不观察到过期的 old->new 映射。
|
||||
assert!(vm.gc_state.forwarding.is_empty());
|
||||
|
||||
let second = plain_object(&mut vm);
|
||||
|
||||
@@ -47,8 +47,8 @@ impl SessionGc {
|
||||
if ptr.is_null() {
|
||||
continue;
|
||||
}
|
||||
// SAFETY: ptrs in session_object_ptrs come only from `session_epoch.alloc` in
|
||||
// `promote_object_inner`, and are valid while the arena is alive.
|
||||
// SAFETY: session_object_ptrs 中的指针只来自 promote_object_inner 的
|
||||
// session_epoch.alloc,在 arena 存活期间有效。
|
||||
unsafe { (*ptr).set_gc_mark(false) };
|
||||
}
|
||||
}
|
||||
@@ -89,7 +89,7 @@ impl SessionGc {
|
||||
if obj.is_data_view_obj() {
|
||||
edges.extend(data_view::data_view_native_edges(obj));
|
||||
}
|
||||
// Traverse upvalue cells for object references
|
||||
// 遍历 upvalue cell 中的对象引用。
|
||||
for cell_ptr in obj.upvalues_slice() {
|
||||
if cell_ptr.is_null() {
|
||||
continue;
|
||||
@@ -102,10 +102,10 @@ impl SessionGc {
|
||||
edges
|
||||
}
|
||||
|
||||
/// Record session-string values held directly by `obj` into `live`. JsStrings hold no GC
|
||||
/// references, so "reaching" a string IS marking it — there is no string DFS stack. Permanent
|
||||
/// strings are also recorded here harmlessly; the sweep only iterates `session_string_ptrs`, so
|
||||
/// non-session pointers in `live` are simply never consulted.
|
||||
/// 把 `obj` 直接持有的 session 字符串值记入 `live`。JsString 不持有 GC 引用,
|
||||
/// 因此"到达"一个字符串就等于标记它——不存在字符串 DFS 栈。永久字符串也会被
|
||||
/// 无害地记录;sweep 只遍历 `session_string_ptrs`,`live` 中的非 session 指针
|
||||
/// 永远不会被查询。
|
||||
fn record_object_string_edges(live: &mut HashSet<*mut JsString, FxBuildHasher>, obj: &JsObject) {
|
||||
if let Some(props) = obj.hash_props_vec() {
|
||||
for value in props.iter() {
|
||||
@@ -120,7 +120,7 @@ impl SessionGc {
|
||||
if obj.home_object().is_string() {
|
||||
live.insert(obj.home_object().as_string_ptr_mut());
|
||||
}
|
||||
// Scan upvalue cells for string references
|
||||
// 扫描 upvalue cell 中的字符串引用。
|
||||
for cell_ptr in obj.upvalues_slice() {
|
||||
if cell_ptr.is_null() {
|
||||
continue;
|
||||
@@ -179,7 +179,7 @@ impl SessionGc {
|
||||
stack.push(ptr);
|
||||
continue;
|
||||
}
|
||||
// SAFETY: object roots are produced by VM-owned fields and builtin objects.
|
||||
// SAFETY: 对象根由 VM 自有的字段与 builtin 对象产生。
|
||||
let obj = unsafe { &*ptr };
|
||||
Self::record_object_string_edges(live_strings, obj);
|
||||
for edge in Self::object_edges(obj) {
|
||||
@@ -196,8 +196,7 @@ impl SessionGc {
|
||||
if ptr.is_null() {
|
||||
continue;
|
||||
}
|
||||
// SAFETY: ptr was discovered from a root/session edge and session root checks require
|
||||
// this to be a valid session object pointer.
|
||||
// SAFETY: ptr 由根/session 边发现,session 根检查保证它是合法 session 对象指针。
|
||||
unsafe {
|
||||
let obj = &mut *ptr;
|
||||
if obj.is_gc_marked() {
|
||||
@@ -223,9 +222,9 @@ impl SessionGc {
|
||||
if obj_ptr.is_null() {
|
||||
return 0;
|
||||
}
|
||||
// SAFETY: `obj_ptr` is verified before calling this helper and points to a session object
|
||||
// owned by the VM session arena. We only reconstruct Boxes that were allocated in
|
||||
// `JsObject::ensure_hash_props`/`ensure_prop_meta` and then drop them once here.
|
||||
// SAFETY: `obj_ptr` 在调用本辅助函数前已校验,指向 VM session arena 拥有的
|
||||
// session 对象。只重建 JsObject::ensure_hash_props/ensure_prop_meta 分配的 Box,
|
||||
// 且只在这里释放一次。
|
||||
unsafe {
|
||||
let obj = &mut *obj_ptr;
|
||||
if require_session {
|
||||
@@ -267,12 +266,13 @@ impl SessionGc {
|
||||
Self::drop_session_object_heap_data(obj_ptr) + size_of::<JsObject>() as u64
|
||||
}
|
||||
|
||||
/// Drop a dead session `JsString` (allocated by `Vm::new_string` via `Box::into_raw`),
|
||||
/// returning the bytes freed. Mirrors `Vm::free_session_string_heap_data`'s drop, but applied
|
||||
/// selectively to a single dead pointer.
|
||||
/// 释放一个已死 session `JsString`(由 `Vm::new_string` 经 `Box::into_raw` 分配),
|
||||
/// 返回释放的字节数。与 `Vm::free_session_string_heap_data` 的释放一致,但只
|
||||
/// 选择性作用于单个已死指针。
|
||||
///
|
||||
/// SAFETY: `ptr` must be a non-null pointer produced by `Box::into_raw(Box::new(JsString))` in
|
||||
/// `Vm::new_string`, still present in `session_string_ptrs`, and dropped exactly once.
|
||||
/// # Safety
|
||||
/// `ptr` 必须是 `Vm::new_string` 中 `Box::into_raw(Box::new(JsString))` 产生的
|
||||
/// 非空指针,仍存在于 `session_string_ptrs`,且恰好释放一次。
|
||||
unsafe fn drop_dead_session_string(ptr: *mut JsString) -> u64 {
|
||||
let bytes = (size_of::<JsString>() + (*ptr).len()) as u64;
|
||||
drop(Box::from_raw(ptr));
|
||||
@@ -291,8 +291,7 @@ impl SessionGc {
|
||||
if old_ptr.is_null() {
|
||||
continue;
|
||||
}
|
||||
// SAFETY: old_ptr comes from session_arena promotions and still points into the old
|
||||
// session arena while sweep runs.
|
||||
// SAFETY: old_ptr 来自 session_arena 的晋升,sweep 运行期间仍指向旧 session arena。
|
||||
let is_live = unsafe { (*old_ptr).is_gc_marked() };
|
||||
if is_live {
|
||||
survivors += 1;
|
||||
@@ -320,7 +319,7 @@ impl SessionGc {
|
||||
}
|
||||
|
||||
for &dst in forwarding.values() {
|
||||
// SAFETY: all pointers in forwarding are newly allocated and initialized objects.
|
||||
// SAFETY: forwarding 中的指针全是新分配且已初始化的对象。
|
||||
let obj = unsafe { &mut *dst };
|
||||
obj.rewrite_object_values(|value| {
|
||||
if value.is_object() {
|
||||
@@ -378,11 +377,11 @@ impl SessionGc {
|
||||
freed_bytes
|
||||
}
|
||||
|
||||
/// Sweep session `JsString`s: keep the pointers recorded live during `mark()`, drop the rest
|
||||
/// via `Box::from_raw`. Live strings are NOT moved — Box addresses are stable — so no forwarding
|
||||
/// map and no root-pointer rewriting are needed. Run after the object sweep (which resets
|
||||
/// `session_bytes_allocated` to object-only), re-adding surviving string bytes. Returns the
|
||||
/// bytes freed.
|
||||
/// 清扫 session `JsString`:保留 `mark()` 阶段记为存活的部分,其余经
|
||||
/// `Box::from_raw` 释放。存活字符串不被搬移——Box 地址稳定——因此无需
|
||||
/// forwarding 表与根指针重写。在对象清扫之后运行(对象清扫会把
|
||||
/// `session_bytes_allocated` 重置为仅对象),再补回存活字符串字节。
|
||||
/// 返回释放的字节数。
|
||||
pub(crate) fn sweep_session_strings(&mut self, vm: &mut Vm) -> u64 {
|
||||
vm_debug!("[GC] sweep strings: {} string ptrs", vm.gc_state.session_string_ptrs.len());
|
||||
let old = std::mem::take(&mut vm.gc_state.session_string_ptrs);
|
||||
@@ -394,12 +393,12 @@ impl SessionGc {
|
||||
continue;
|
||||
}
|
||||
if self.live_strings.contains(&ptr) {
|
||||
// Survivor — address unchanged, no rewrite needed.
|
||||
// SAFETY: ptr is a live session-string box still owned by the VM.
|
||||
// 存活——地址不变,无需重写。
|
||||
// SAFETY: ptr 是仍归 VM 所有的存活 session 字符串 box。
|
||||
live_bytes += unsafe { size_of::<JsString>() + (*ptr).len() };
|
||||
live.push(ptr);
|
||||
} else {
|
||||
// SAFETY: ptr is in session_string_ptrs but not reachable; drop exactly once.
|
||||
// SAFETY: ptr 在 session_string_ptrs 中但不可达,恰好释放一次。
|
||||
freed += unsafe { Self::drop_dead_session_string(ptr) };
|
||||
}
|
||||
}
|
||||
@@ -521,12 +520,12 @@ fn rewrite_vm_roots(vm: &mut Vm, forwarding: &HashMap<*mut JsObject, *mut JsObje
|
||||
*value = rewrite_forwarded_value(*value, forwarding);
|
||||
}
|
||||
vm.iters.last_for_of_result = rewrite_forwarded_value(vm.iters.last_for_of_result, forwarding);
|
||||
// Converted immutables are non-session (scalars + perm-strings) — not rewritten.
|
||||
// 已转换的不可变常量不是 session 值(标量 + perm 字符串)——不重写。
|
||||
for iter in &mut vm.iters.for_in_iters {
|
||||
if iter.is_null() {
|
||||
continue;
|
||||
}
|
||||
// SAFETY: for_in_iters stores live iterator pointers owned by the current VM epoch.
|
||||
// SAFETY: for_in_iters 存放由当前 VM epoch 拥有的存活迭代器指针。
|
||||
unsafe {
|
||||
for (value, _si) in (*(*iter)).keys.iter_mut() {
|
||||
*value = rewrite_forwarded_value(*value, forwarding);
|
||||
@@ -535,7 +534,7 @@ fn rewrite_vm_roots(vm: &mut Vm, forwarding: &HashMap<*mut JsObject, *mut JsObje
|
||||
}
|
||||
let global_ptr = vm.session.global_object().as_ptr() as *mut JsObject;
|
||||
if !global_ptr.is_null() {
|
||||
// SAFETY: KernelSession owns global_object for the VM lifetime.
|
||||
// SAFETY: KernelSession 在 VM 生命周期内拥有 global_object。
|
||||
unsafe {
|
||||
(*global_ptr).rewrite_object_values(|value| rewrite_forwarded_value(value, forwarding));
|
||||
}
|
||||
@@ -689,8 +688,8 @@ mod tests {
|
||||
let _ = gc.sweep(&mut vm);
|
||||
vm.gc_state.session_gc = gc;
|
||||
|
||||
// The reused forwarding map MUST be cleared after sweep, otherwise
|
||||
// promote would observe stale old->new entries pointing into the freed arena.
|
||||
// 复用的 forwarding 表必须在 sweep 后清空,否则 promote 会观察到指向已释放
|
||||
// arena 的过期 old->new 条目。
|
||||
assert!(vm.gc_state.forwarding.is_empty());
|
||||
}
|
||||
|
||||
@@ -934,7 +933,7 @@ mod tests {
|
||||
let dead_ptr = dead.as_string_ptr_mut();
|
||||
assert!(vm.gc_state.session_string_ptrs.contains(&dead_ptr));
|
||||
|
||||
// No root references `dead` (it lives only on the Rust stack as a value wrapper).
|
||||
// 没有根引用 `dead`(它只作为值包装存在于 Rust 栈上)。
|
||||
collect(&mut vm);
|
||||
|
||||
assert!(!vm.gc_state.session_string_ptrs.contains(&dead_ptr));
|
||||
@@ -950,7 +949,7 @@ mod tests {
|
||||
collect(&mut vm);
|
||||
|
||||
assert!(vm.gc_state.session_string_ptrs.contains(&live_ptr));
|
||||
// Live strings are never moved — the register still points at the same box.
|
||||
// 存活字符串永不搬移——寄存器仍指向同一 box。
|
||||
assert_eq!(vm.regs[0].as_string_ptr_mut(), live_ptr);
|
||||
assert_eq!(unsafe { (*live_ptr).as_str() }, "live-in-reg");
|
||||
}
|
||||
@@ -966,7 +965,7 @@ mod tests {
|
||||
}
|
||||
let obj_session = vm.promote_object(obj);
|
||||
|
||||
// The string is reachable ONLY through the live object's property, not via any register.
|
||||
// 该字符串仅通过存活对象的属性可达,不经过任何寄存器。
|
||||
vm.regs.fill(JsValue::undefined());
|
||||
vm.regs[0] = JsValue::from_js_object(obj_session);
|
||||
|
||||
@@ -981,13 +980,13 @@ mod tests {
|
||||
let mut vm = Vm::new();
|
||||
let perm = vm.perm_string("perm");
|
||||
let perm_ptr = perm.as_string_ptr_mut();
|
||||
// Permanent strings live in PermInterner, never in the session set.
|
||||
// 永久字符串位于 PermInterner,绝不在 session 集合中。
|
||||
assert!(!vm.gc_state.session_string_ptrs.contains(&perm_ptr));
|
||||
vm.regs[0] = perm;
|
||||
|
||||
collect(&mut vm);
|
||||
|
||||
// Never dropped by the session sweep (still readable, still absent from session set).
|
||||
// 绝不被 session 清扫释放(仍可读,仍不在 session 集合中)。
|
||||
assert!(!vm.gc_state.session_string_ptrs.contains(&perm_ptr));
|
||||
assert_eq!(unsafe { (*perm_ptr).as_str() }, "perm");
|
||||
}
|
||||
|
||||
+50
-51
@@ -22,11 +22,11 @@ use oxide_types::value::{JsValue, PTR_MASK};
|
||||
|
||||
pub(crate) const MAX_PROTO_CHAIN_DEPTH: usize = 1024;
|
||||
|
||||
/// Convert a `NativeFnPtr` to a callable `NativeFn`.
|
||||
/// 将 [`NativeFnPtr`] 转换为可调用的 [`NativeFn`]。
|
||||
///
|
||||
/// # Safety
|
||||
/// The pointer stored in `ptr` must have been created from a valid `NativeFn` fn-item.
|
||||
/// This is the single point in the codebase where `NativeFnPtr → NativeFn` coercion happens.
|
||||
/// `ptr` 必须由合法的 `NativeFn` 函数项产生。
|
||||
/// 这是代码库中唯一一处 `NativeFnPtr → NativeFn` 的强制转换点。
|
||||
#[inline(always)]
|
||||
pub(crate) unsafe fn native_fn_ptr_to_fn(ptr: NativeFnPtr) -> NativeFn {
|
||||
std::mem::transmute::<*const (), NativeFn>(ptr.as_ptr())
|
||||
@@ -110,7 +110,7 @@ pub struct CallFrame {
|
||||
pub function_name: u32,
|
||||
pub caller_reg_limit: u8,
|
||||
pub saved_reg_offset: u32,
|
||||
/// push 时记录 spill_stack 长度(D-08 帧边界,SPILL/UNSPILL 基址)。
|
||||
/// 记录本帧 spill 栈起始长度,作为 SPILL/UNSPILL 的帧边界基址(push 时快照)。
|
||||
pub spill_offset: u32,
|
||||
pub saved_this: JsValue,
|
||||
pub saved_new_target: JsValue,
|
||||
@@ -125,8 +125,8 @@ pub struct CallFrame {
|
||||
///
|
||||
/// 每个 key 与它的 intern id 配对保存,使整型下标 key 无需重新 intern 即可排到字符串 key 之前。
|
||||
pub struct ForInIter<'bump> {
|
||||
/// Each key paired with its string-intern id, so for-in can sort
|
||||
/// integer-index keys ahead of string keys without re-interning.
|
||||
/// 每个 key 与其字符串 intern id 配对,使 for-in 排序时整型下标 key 无需
|
||||
/// 重新 intern 即可排到字符串 key 之前。
|
||||
pub keys: bumpalo::collections::Vec<'bump, (JsValue, u32)>,
|
||||
pub index: usize,
|
||||
}
|
||||
@@ -136,13 +136,13 @@ pub struct TryHandler {
|
||||
pub catch_pc: Option<usize>,
|
||||
pub finally_pc: Option<usize>,
|
||||
pub frame_depth: usize,
|
||||
/// for_of_iters length at try entry — bounds which iterators IteratorClose on unwind.
|
||||
/// try 入口时 for_of_iters 的长度,界定异常展开时哪些迭代器需要 IteratorClose。
|
||||
pub for_of_depth: usize,
|
||||
}
|
||||
|
||||
/// Heap-allocated snapshot used by `call_bytecode_function_inline`.
|
||||
/// Keeping it on the heap prevents Rust stack overflow when JS code
|
||||
/// chains multiple sync bytecode calls (e.g. sort comparator, accessor).
|
||||
/// `call_bytecode_function_inline` 使用的堆分配快照。
|
||||
/// 放在堆上避免 JS 代码链式同步字节码调用(如 sort 比较器、accessor)时
|
||||
/// 耗尽 Rust 栈。
|
||||
pub(crate) struct InlineSyncState {
|
||||
pub(crate) regs: Box<[JsValue; 256]>,
|
||||
pub(crate) pc: usize,
|
||||
@@ -174,12 +174,12 @@ pub struct Vm {
|
||||
pub(crate) regs: [JsValue; 256],
|
||||
pub(crate) pc: usize,
|
||||
pub(crate) bytecode: Vec<opcode::Instr>,
|
||||
/// Per-run convert-once immutables cache. Index 0 = top module, sub_idx+1 = sub_modules[sub_idx].
|
||||
/// Each `OnceLock` holds that module's constants converted to `JsValue`s exactly once this run.
|
||||
/// Rebuilt every `run()`. Immutables are scalars + perm-strings — read-only, never GC roots.
|
||||
/// 每次 run 转换一次的不可变常量缓存。下标 0 = 顶层模块,sub_idx+1 = sub_modules[sub_idx]。
|
||||
/// 每个 `OnceLock` 保存该模块常量本次运行中只转换一次的 `JsValue` 结果,每次 `run()` 重建。
|
||||
/// 不可变常量是标量 + perm 字符串,只读,不作为 GC 根。
|
||||
pub(crate) immutables_cache: Vec<OnceLock<Vec<JsValue>>>,
|
||||
/// Read-only view into the currently-active module's converted immutables (inside immutables_cache).
|
||||
/// A fat `*const` because the cache Vec is VM-owned and run-stable (OnceLock filled once).
|
||||
/// 当前活动模块已转换不可变常量的只读视图(指向 immutables_cache 内部)。
|
||||
/// 用胖 `*const`:缓存 Vec 归 VM 所有且本次运行稳定(OnceLock 只填一次)。
|
||||
pub(crate) active_immutables: *const [JsValue],
|
||||
pub(crate) frames: SmallVec<[CallFrame; 16]>,
|
||||
pub(crate) kernel_core: Arc<KernelCore>,
|
||||
@@ -190,40 +190,40 @@ pub struct Vm {
|
||||
pub(crate) sub_modules: Arc<Vec<CompiledModule>>,
|
||||
pub(crate) saved_bytecode_stack: Vec<Vec<opcode::Instr>>,
|
||||
pub(crate) saved_immutables_stack: Vec<*const [JsValue]>,
|
||||
/// Shared register save-stack. Each active `CallFrame` saved its caller's live
|
||||
/// registers (`regs[..caller_reg_limit]`) here at `saved_reg_offset`; restore copies
|
||||
/// them back and truncates. Capacity is retained across calls — zero per-call heap alloc.
|
||||
/// 共享寄存器保存栈。每个活动 `CallFrame` 在 push 时把调用方活跃寄存器
|
||||
/// (`regs[..caller_reg_limit]`)按 `saved_reg_offset` 存到这里;恢复时复制回
|
||||
/// 并截断。容量跨调用保留,避免每次调用堆分配。
|
||||
pub(crate) save_stack: Vec<JsValue>,
|
||||
/// VM 级 spill 栈(D-08,仿 save_stack 帧边界)。`CallFrame.spill_offset` 定位本帧区,
|
||||
/// 调用子函数时从边界后分配,restore_frame 截断恢复。
|
||||
/// VM 级 spill 栈。`CallFrame.spill_offset` 定位本帧区:调用子函数时从边界后分配,
|
||||
/// 帧恢复时截断到边界,子函数 spill 数据随帧丢弃。
|
||||
pub(crate) spill_stack: Vec<JsValue>,
|
||||
pub(crate) try_stack: Vec<TryHandler>,
|
||||
pub(crate) exception_value: Option<JsValue>,
|
||||
/// Side-channel carrying the JsValue thrown by a sync call whose error was flattened
|
||||
/// to a String by `call_function_sync`/`unwind`, so for-of can re-throw the original
|
||||
/// value. Plain field (NOT in InlineSyncState) so it survives the inline-call restore.
|
||||
/// 同步调用抛出的原始 JsValue 侧通道:`call_function_sync`/`unwind` 把错误展平为
|
||||
/// String 后,for-of 需要重新抛出原值。放在 VM 顶层字段(不在 InlineSyncState 中)
|
||||
/// 以便跨内联调用的恢复过程存活。
|
||||
pub(crate) last_uncaught_value: Option<JsValue>,
|
||||
pub(crate) pending_exception: Option<JsValue>,
|
||||
pub(crate) pending_error_kind: Option<&'static str>,
|
||||
pub(crate) root_reg_limit: u8,
|
||||
pub(crate) active_reg_limit: u8,
|
||||
pub(crate) native_call_depth: usize,
|
||||
/// Set to Some(target_reg) when `ordinary_get` pushes a bytecode accessor frame.
|
||||
/// The dispatch loop checks this flag and skips writing `regs[target_reg]` from the
|
||||
/// call result — the value will be delivered by the RETURN handler instead.
|
||||
/// `ordinary_get` 压入字节码 accessor 帧时设为 Some(target_reg)。
|
||||
/// 调度循环检查该标志,跳过用调用结果写 `regs[target_reg]` —— 值改由 RETURN
|
||||
/// 处理器交付。
|
||||
pub(crate) accessor_frame_target_reg: Option<u8>,
|
||||
/// Grouped session-arena / GC bookkeeping.
|
||||
/// 分组保存 session arena / GC 簿记状态。
|
||||
pub(crate) gc_state: GcState,
|
||||
/// Grouped `Symbol` interning state.
|
||||
/// 分组保存 `Symbol` intern 状态。
|
||||
pub(crate) symbols: SymbolState,
|
||||
/// Grouped live `for-in` / `for-of` iterator state.
|
||||
/// 分组保存活跃的 for-in / for-of 迭代器状态。
|
||||
pub(crate) iters: IterState,
|
||||
/// Grouped inline-cache and instruction counters.
|
||||
/// 分组保存 inline cache 与指令计数器。
|
||||
pub(crate) profiling: ProfilingState,
|
||||
pub(crate) sub_module_stack: Vec<Arc<Vec<CompiledModule>>>,
|
||||
pub(crate) cell_stack: Vec<Vec<*mut Cell>>,
|
||||
pub(crate) temp_immutables: Vec<Vec<JsValue>>,
|
||||
/// Reusable string buffer for concatenation to avoid allocation per `+` op.
|
||||
/// 可复用字符串缓冲区,避免每次 `+` 拼接都分配。
|
||||
pub(crate) string_buf: String,
|
||||
}
|
||||
|
||||
@@ -342,7 +342,7 @@ impl Vm {
|
||||
if obj_ptr.is_null() {
|
||||
return false;
|
||||
}
|
||||
// SAFETY: obj_ptr is non-null and points to a `JsObject` owned by this session.
|
||||
// SAFETY: obj_ptr 非空且指向本 session 拥有的 JsObject。
|
||||
unsafe { (*obj_ptr).is_session_epoch() }
|
||||
}
|
||||
|
||||
@@ -352,25 +352,25 @@ impl Vm {
|
||||
ptr
|
||||
}
|
||||
|
||||
/// Read-only view into the active module's converted immutables. Empty before any `run()`.
|
||||
/// 活动模块已转换不可变常量的只读视图;任何 `run()` 之前为空。
|
||||
#[inline(always)]
|
||||
pub(crate) fn immutables(&self) -> &[JsValue] {
|
||||
if self.active_immutables.is_null() {
|
||||
&[]
|
||||
} else {
|
||||
// SAFETY: active_immutables points into a OnceLock<Vec<JsValue>> inside immutables_cache,
|
||||
// which the VM owns; the Vec is filled once and never reallocated for the run's lifetime.
|
||||
// SAFETY: active_immutables 指向 VM 拥有的 immutables_cache 内的 OnceLock<Vec<JsValue>>,
|
||||
// Vec 只填充一次,本次运行期间不再重分配。
|
||||
unsafe { &*self.active_immutables }
|
||||
}
|
||||
}
|
||||
|
||||
/// Activate module `cache_idx`'s immutables (0 = top, sub_idx+1 = sub_modules[sub_idx]),
|
||||
/// converting them once into `immutables_cache[cache_idx]` and pointing `active_immutables` at
|
||||
/// the cached Vec. `constants` is passed by the caller (it already holds `&module.constants`).
|
||||
/// 激活模块 `cache_idx` 的不可变常量(0 = 顶层,sub_idx+1 = sub_modules[sub_idx]),
|
||||
/// 只转换一次存入 `immutables_cache[cache_idx]`,并把 `active_immutables` 指向该 Vec。
|
||||
/// `constants` 由调用方传入(它已持有 `&module.constants`)。
|
||||
pub(crate) fn activate_immutables(&mut self, cache_idx: usize, constants: &[Constant]) {
|
||||
// Raw-ptr the cache slot so `get_or_init` (which borrows immutables_cache) and the &self
|
||||
// convert_immutables closure don't conflict with the subsequent self.active_immutables write.
|
||||
// Sound: immutables_cache is VM-owned, read-only, and run-stable.
|
||||
// 用裸指针访问缓存槽,避免 get_or_init(借用 immutables_cache)与 &self 的
|
||||
// convert_immutables 闭包和随后对 active_immutables 的写入发生借用冲突。
|
||||
// 成立前提:immutables_cache 归 VM 所有、只读、本次运行稳定。
|
||||
let slot: *const OnceLock<Vec<JsValue>> = &self.immutables_cache[cache_idx];
|
||||
let vec = unsafe { &*slot }.get_or_init(|| self.convert_immutables(constants));
|
||||
self.active_immutables = vec.as_slice() as *const [JsValue];
|
||||
@@ -413,7 +413,7 @@ impl Vm {
|
||||
f(v);
|
||||
}
|
||||
f(self.iters.last_for_of_result);
|
||||
// Converted immutables (scalars + perm-strings) are NOT session GC roots — not scanned.
|
||||
// 已转换的不可变常量(标量 + perm 字符串)不作为 session GC 根,不参与扫描。
|
||||
for iter in &self.iters.for_in_iters {
|
||||
if iter.is_null() {
|
||||
continue;
|
||||
@@ -569,7 +569,7 @@ impl Vm {
|
||||
if !val.is_string() {
|
||||
return None;
|
||||
}
|
||||
// SAFETY: val is a string value; its JsString pointer is alive for its lifetime.
|
||||
// SAFETY: val 是字符串值,其 JsString 指针在生命周期内有效。
|
||||
Some(unsafe { (*val.as_string_ptr()).data.clone() })
|
||||
}
|
||||
|
||||
@@ -599,7 +599,7 @@ impl Vm {
|
||||
|
||||
pub(crate) fn property_key_si(&mut self, val: JsValue) -> u32 {
|
||||
if val.is_string() {
|
||||
// SAFETY: val is a string value; bridge its content to a permanent key id.
|
||||
// SAFETY: val 是字符串值,把其内容桥接为永久 key id。
|
||||
let s = unsafe { &(*val.as_string_ptr()).data };
|
||||
return self.kernel_core.perm_interner().intern(s).0;
|
||||
}
|
||||
@@ -702,8 +702,8 @@ impl Vm {
|
||||
}
|
||||
let saved_regs = self.regs;
|
||||
let arg_regs = self.pack_sync_native_call_args(receiver, callee, args);
|
||||
// SAFETY: native_fn was set via set_native_fn with a valid NativeFn pointer;
|
||||
// native_fn_ptr_to_fn is the single coercion point for NativeFnPtr → NativeFn.
|
||||
// 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);
|
||||
@@ -719,10 +719,9 @@ impl Vm {
|
||||
};
|
||||
}
|
||||
|
||||
// Run bytecode function inline on self (same epoch) to prevent use-after-free.
|
||||
// A separate sub-VM would own a different epoch; returning a JsValue that contains
|
||||
// a pointer into the sub-VM epoch and then dropping the sub-VM causes a dangling
|
||||
// pointer / access violation in release builds.
|
||||
// 字节码函数在自身(同一 epoch)内联执行,防止 use-after-free。
|
||||
// 独立子 VM 拥有不同 epoch:返回含子 VM epoch 指针的 JsValue 后再销毁子 VM,
|
||||
// 会在 release 构建中产生悬垂指针 / 访问违规。
|
||||
self.call_bytecode_function_inline(callee, callee_obj, receiver, args)
|
||||
}
|
||||
|
||||
@@ -1448,7 +1447,7 @@ mod tests {
|
||||
let proto = vm.session.builtin_world().function_proto.as_ptr() as *mut JsObject;
|
||||
let mut obj = JsObject::new_empty(oxide_kernel::shape_forge::EMPTY_SHAPE_ID, JsValue::from_js_object(proto));
|
||||
obj.set_function(true);
|
||||
// SAFETY: f is a NativeFn fn-item; valid to store as NativeFnPtr.
|
||||
// SAFETY: f 是 NativeFn 函数项,可作为 NativeFnPtr 存储。
|
||||
obj.set_native_fn(Some(unsafe { NativeFnPtr::from_raw(f as *const ()) }));
|
||||
JsValue::object(vm.alloc_object(obj) as *mut u8)
|
||||
}
|
||||
|
||||
@@ -179,7 +179,7 @@ impl Vm {
|
||||
let reg = (seg & 0x7F) as u8;
|
||||
let val = self.regs[reg as usize];
|
||||
let s = if val.is_string() {
|
||||
// SAFETY: val is a string value.
|
||||
// SAFETY: val 是字符串值。
|
||||
unsafe { (*val.as_string_ptr()).data.clone() }
|
||||
} else {
|
||||
format!("{}", val)
|
||||
@@ -191,7 +191,7 @@ impl Vm {
|
||||
if const_idx < imm.len() {
|
||||
let val = imm[const_idx];
|
||||
if val.is_string() {
|
||||
// SAFETY: val is a string value.
|
||||
// SAFETY: val 是字符串值。
|
||||
let s = unsafe { (*val.as_string_ptr()).data.clone() };
|
||||
result.push_str(&s);
|
||||
}
|
||||
@@ -285,7 +285,7 @@ impl Vm {
|
||||
vm_trace!("FOR_IN_INIT r{}={:?}", a, self.regs[a]);
|
||||
let obj_val = self.regs[a];
|
||||
if obj_val.is_null() || obj_val.is_undefined() {
|
||||
// null/undefined enumerate nothing — an empty for-in, not a TypeError.
|
||||
// null/undefined 枚举不到任何键——是空 for-in,而非 TypeError。
|
||||
let keys_vec: bumpalo::collections::Vec<(JsValue, u32)> =
|
||||
bumpalo::collections::Vec::new_in(self.epoch.bump());
|
||||
let iter = self.epoch.alloc(ForInIter { keys: keys_vec, index: 0 });
|
||||
@@ -293,7 +293,7 @@ impl Vm {
|
||||
return Ok(());
|
||||
}
|
||||
if !obj_val.is_object() {
|
||||
// ToObject coercion for other primitives is not implemented yet — TypeError is correct until it lands.
|
||||
// 未支持:其它基本类型的 ToObject 强转尚未实现,在此之前抛 TypeError 是正确行为。
|
||||
return self.raise_type_error("for-in right-hand side is not an object");
|
||||
}
|
||||
|
||||
@@ -302,9 +302,8 @@ impl Vm {
|
||||
let mut seen = std::collections::HashSet::new();
|
||||
let mut current = obj_val;
|
||||
|
||||
// Arrays store integer-indexed elements in prop_vec, which is not part of
|
||||
// the shape chain. Enumerate them (ES: array indices are enumerable string
|
||||
// keys, ordered ascending before other own keys).
|
||||
// 数组把整型下标元素存在 prop_vec,而 prop_vec 不属于 shape 链。
|
||||
// 单独枚举它们(ES:数组下标是可枚举字符串键,按升序排在其它自有键之前)。
|
||||
if current.is_object() {
|
||||
let arr = unsafe { &*current.as_js_object_ptr() };
|
||||
if arr.is_array() {
|
||||
@@ -315,10 +314,7 @@ impl Vm {
|
||||
.unwrap_or(PropAttributes::DEFAULT_DATA.enumerable());
|
||||
if is_enum {
|
||||
let idx = self.kernel_core.perm_interner().intern(&i.to_string()).0;
|
||||
keys_vec.push((
|
||||
JsValue::perm_string(self.kernel_core.perm_interner().string_ptr(idx)),
|
||||
idx,
|
||||
));
|
||||
keys_vec.push((JsValue::perm_string(self.kernel_core.perm_interner().string_ptr(idx)), idx));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -365,14 +361,13 @@ impl Vm {
|
||||
break;
|
||||
}
|
||||
}
|
||||
// The shape chain is walked leaf->root (reverse of insertion order);
|
||||
// flip this object's slice back to insertion order before its proto.
|
||||
// shape 链按叶→根遍历(与插入序相反);在接上原型前把本对象的切片
|
||||
// 翻转为插入序。
|
||||
keys_vec[obj_start..].reverse();
|
||||
current = cur.proto();
|
||||
}
|
||||
|
||||
// ES enumeration order: integer-index keys ascending, then the rest in
|
||||
// insertion order. Stable sort preserves insertion order among non-index keys.
|
||||
// ES 枚举序:整型下标键升序,其余按插入序。稳定排序保持非下标键间的插入序。
|
||||
keys_vec.sort_by(|(_, a_si), (_, b_si)| {
|
||||
match (self.array_index_from_property_key(*a_si), self.array_index_from_property_key(*b_si)) {
|
||||
(Some(ai), Some(bi)) => ai.cmp(&bi),
|
||||
@@ -488,9 +483,9 @@ impl Vm {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// next()/value access threw: route through unwind so a surrounding try/catch can catch
|
||||
/// it, re-throwing the ORIGINAL value when available. Per ECMA-262 the iterator is NOT
|
||||
/// closed via return() on a next()-throw — pop it so the unwinding IteratorClose pass skips it.
|
||||
/// next()/value 访问抛出:经 unwind 走异常展开,使外围 try/catch 能捕获,并尽可能
|
||||
/// 重新抛出原始值。按 ECMA-262,next() 抛出时不会经 return() 关闭迭代器——
|
||||
/// 先弹出它,使展开时的 IteratorClose 遍历跳过该迭代器。
|
||||
fn throw_for_of_error(&mut self, msg: String) -> Result<(), String> {
|
||||
self.iters.pop_for_of();
|
||||
let exc = match self.last_uncaught_value.take() {
|
||||
@@ -507,14 +502,16 @@ impl Vm {
|
||||
let Some(iterator) = self.iters.pop_for_of() else {
|
||||
return Ok(());
|
||||
};
|
||||
// Normal / break / return exit: no prior abrupt completion, so return()'s own throw propagates.
|
||||
// 正常 / break / return 退出:此前无进行中的突然完成,return() 自身的抛出直接传播。
|
||||
self.close_for_of_iterator(iterator, false)
|
||||
}
|
||||
|
||||
/// Call `iterator.return()` (IteratorClose). When `suppress_return_error` is true the call
|
||||
/// is being made because an enclosing abrupt completion is unwinding: return()'s own result
|
||||
/// is discarded and the in-flight exception is preserved across the call. When false (normal
|
||||
/// for-of exit) return()'s error propagates.
|
||||
/// 调用 `iterator.return()`(IteratorClose)。
|
||||
///
|
||||
/// # 边界与前提
|
||||
/// - `suppress_return_error` 为 true 时表示正在展开某个外围突然完成:return() 的
|
||||
/// 自身结果被丢弃,并保留在途异常跨调用存活。
|
||||
/// - 为 false 时(正常 for-of 退出)return() 的错误向外传播。
|
||||
pub(crate) fn close_for_of_iterator(
|
||||
&mut self, iterator: JsValue, suppress_return_error: bool,
|
||||
) -> Result<(), String> {
|
||||
@@ -549,9 +546,9 @@ impl Vm {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// IteratorClose every active for-of iterator above `depth`, used by `unwind()` to close
|
||||
/// loops abandoned by a throw. Pops before closing so a re-entrant call can't re-close,
|
||||
/// and so the next()-throw path (which already popped its own iterator) is skipped.
|
||||
/// 对 `depth` 以上的所有活跃 for-of 迭代器执行 IteratorClose,供 `unwind()` 关闭
|
||||
/// 被异常中断的循环。先弹出再关闭,防止重入调用重复关闭,同时跳过 next()-throw
|
||||
/// 路径(它已弹出自己的迭代器)。
|
||||
pub(crate) fn close_for_of_above(&mut self, depth: usize) {
|
||||
while self.iters.for_of_iters.len() > depth {
|
||||
let Some(iterator) = self.iters.pop_for_of() else {
|
||||
@@ -574,7 +571,7 @@ impl Vm {
|
||||
.get(excluded_idx)
|
||||
.and_then(|v| {
|
||||
if v.is_string() {
|
||||
// SAFETY: v is a string constant value.
|
||||
// SAFETY: v 是字符串常量值。
|
||||
Some(unsafe { (*v.as_string_ptr()).data.clone() })
|
||||
} else {
|
||||
None
|
||||
|
||||
@@ -124,7 +124,7 @@ impl Vm {
|
||||
self.ordinary_set_inner(obj, prop_name_si, val, receiver, false)
|
||||
}
|
||||
|
||||
/// Dispatch-time entry: caller (dispatch_set_prop etc.) already promoted the value.
|
||||
/// 分发期入口:调用方(dispatch_set_prop 等)已对值做过 promote。
|
||||
pub(crate) fn ordinary_set_dispatch(
|
||||
&mut self, obj: &mut JsObject, prop_name_si: u32, val: JsValue, receiver: JsValue,
|
||||
) -> Result<(), String> {
|
||||
@@ -235,17 +235,17 @@ impl Vm {
|
||||
Ok(val)
|
||||
}
|
||||
|
||||
/// Perform ordinary_get and write back IC with proto chain depth.
|
||||
/// 执行 ordinary_get 并把 IC 按原型链深度写回。
|
||||
fn proto_chain_ic_get(&mut self, obj: &JsObject, prop_name_si: u32, receiver: JsValue) -> Result<JsValue, String> {
|
||||
let resolved = self.ordinary_get(obj, prop_name_si, receiver)?;
|
||||
// Fast path: own property (depth=0).
|
||||
// 快路径:自身属性(depth=0)。
|
||||
if let Some(pos) = self.kernel_core.shape_forge().lookup_position(obj.shape_id(), prop_name_si) {
|
||||
if !obj.is_accessor_meta(pos) {
|
||||
crate::ic_helper::write_ic_back(&mut self.bytecode, self.pc, obj.shape_id(), pos, 0);
|
||||
}
|
||||
return Ok(resolved);
|
||||
}
|
||||
// Walk proto chain for inherited properties.
|
||||
// 沿原型链查找继承属性。
|
||||
let mut cursor = obj.proto().as_js_object_ptr();
|
||||
let mut depth = 1u8;
|
||||
while !cursor.is_null() {
|
||||
|
||||
@@ -138,7 +138,7 @@ impl Vm {
|
||||
let len = frame.caller_reg_limit as usize;
|
||||
self.regs[..len].copy_from_slice(&self.save_stack[offset..offset + len]);
|
||||
self.save_stack.truncate(offset);
|
||||
// spill 帧边界截断(D-08,仿 save_stack 窗口):子函数 spill 数据在帧恢复后丢弃
|
||||
// spill 帧边界截断:子函数写入的 spill 数据在帧恢复后丢弃。
|
||||
self.spill_stack.truncate(frame.spill_offset as usize);
|
||||
self.regs[254] = frame.saved_this;
|
||||
self.regs[255] = frame.saved_new_target;
|
||||
@@ -192,7 +192,7 @@ impl Vm {
|
||||
self.restore_frame(frame);
|
||||
}
|
||||
}
|
||||
// IteratorClose any for-of loops abandoned inside this handler's scope.
|
||||
// 对该处理器作用域内被中断的 for-of 循环执行 IteratorClose。
|
||||
self.close_for_of_above(handler.for_of_depth);
|
||||
if let Some(finally_pc) = handler.finally_pc {
|
||||
vm_trace!("unwind: entering finally at pc={}", finally_pc);
|
||||
@@ -216,8 +216,8 @@ impl Vm {
|
||||
self.restore_frame(frame);
|
||||
}
|
||||
let exc = self.exception_value.take().unwrap_or(JsValue::undefined());
|
||||
// Preserve the escaping JsValue so a for-of next()-throw can re-throw the original
|
||||
// value instead of the flattened string (consumed by dispatch_for_of_done/next).
|
||||
// 保留逃逸的 JsValue,使 for-of 的 next() 抛出时可重新抛出原值而非展平后的
|
||||
// 字符串(由 dispatch_for_of_done/next 消费)。
|
||||
self.last_uncaught_value = Some(exc);
|
||||
let kind_str = self.pending_error_kind.take().unwrap_or("Error");
|
||||
let exc_text = self.error_text(exc);
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
//! Grouped `Vm` state sub-structs.
|
||||
//! `Vm` 的状态子结构分组。
|
||||
//!
|
||||
//! `Vm` owns these as fields (`gc_state`, `symbols`, `iters`, `profiling`) so
|
||||
//! that workers refactoring one subsystem touch one struct instead of the
|
||||
//! central `Vm` god-struct. Per the project's parallel-dev decisions:
|
||||
//! Symbol/Iter/Profiling are full sinks (their logic is self-contained), while
|
||||
//! `GcState` is field-classification only — GC `mark`/`sweep`/`alloc_object`
|
||||
//! scan roots across *all* sub-structs and stay as `&mut Vm` methods.
|
||||
//! `Vm` 以字段持有这些子结构(`gc_state`、`symbols`、`iters`、`profiling`),
|
||||
//! 使各子系统维护者只需改动一个结构而非庞大的 `Vm` 主结构。约定:
|
||||
//! Symbol/Iter/Profiling 为完整子模块(逻辑自包含);`GcState` 仅做字段分类——
|
||||
//! GC 的 mark/sweep/alloc_object 需要扫描跨全部子结构的根(regs、frames、
|
||||
//! 迭代器等),仍以 `&mut Vm` 方法驻留。
|
||||
//!
|
||||
//! This file defines fields only; methods live with `Vm`.
|
||||
//! 本文件只定义字段;方法随 `Vm` 存放。
|
||||
|
||||
use std::cell::Cell;
|
||||
use std::collections::HashMap;
|
||||
@@ -20,11 +19,11 @@ use oxide_types::value::JsValue;
|
||||
use crate::session_gc::SessionGc;
|
||||
use crate::vm::ForInIter;
|
||||
|
||||
/// Session-arena and garbage-collection bookkeeping.
|
||||
/// session arena 与 GC 簿记。
|
||||
///
|
||||
/// Field-classify only. `mark`/`sweep`/`rewrite_vm_roots` live on `Vm` because
|
||||
/// GC scans all sub-struct roots (regs, frames, for_in_iters, for_of_iters,
|
||||
/// exception_value, etc.) — they cannot be confined to GcState.
|
||||
/// 仅做字段分类。mark/sweep/rewrite_vm_roots 驻留在 `Vm` 上:GC 需扫描所有
|
||||
/// 子结构的根(regs、frames、for_in_iters、for_of_iters、exception_value 等),
|
||||
/// 无法限制在 GcState 内。
|
||||
pub(crate) struct GcState {
|
||||
pub(crate) session_epoch: bumpalo::Bump,
|
||||
pub(crate) session_gc: SessionGc,
|
||||
@@ -41,7 +40,7 @@ impl GcState {
|
||||
}
|
||||
}
|
||||
|
||||
/// `Symbol` interning state.
|
||||
/// Symbol 的 intern 状态。
|
||||
pub(crate) struct SymbolState {
|
||||
pub(crate) symbol_counter: u32,
|
||||
pub(crate) symbol_descriptions: Vec<String>,
|
||||
@@ -83,7 +82,7 @@ impl SymbolState {
|
||||
}
|
||||
}
|
||||
|
||||
/// Live iterator state for `for-in` / `for-of`.
|
||||
/// for-in / for-of 的活跃迭代器状态。
|
||||
pub(crate) struct IterState {
|
||||
pub(crate) for_in_iters: Vec<*mut ForInIter<'static>>,
|
||||
pub(crate) for_of_iters: Vec<JsValue>,
|
||||
@@ -134,7 +133,7 @@ impl IterState {
|
||||
}
|
||||
}
|
||||
|
||||
/// Inline-cache hit/miss and instruction counters.
|
||||
/// inline cache 命中/未命中计数与指令计数。
|
||||
pub(crate) struct ProfilingState {
|
||||
pub(crate) ic_hits: Cell<u64>,
|
||||
pub(crate) ic_misses: Cell<u64>,
|
||||
|
||||
@@ -203,11 +203,11 @@ impl Vm {
|
||||
}
|
||||
|
||||
pub(crate) fn clear_execution_state(&mut self) {
|
||||
// Reset contract:
|
||||
// - Clears register file, pc, frame/iterator stacks, saved execution stacks,
|
||||
// try handlers, pending exceptions, and native call depth.
|
||||
// - Leaves kernel-owned shared state intact.
|
||||
// - `reset()` additionally clears bytecode/constants and resets epoch ownership.
|
||||
// 重置契约:
|
||||
// - 清空寄存器文件、pc、帧/迭代器栈、保存的执行栈、try 处理器、
|
||||
// 待处理异常与 native 调用深度。
|
||||
// - 保留 kernel 共享状态不变。
|
||||
// - `reset()` 额外清空 bytecode/constants 并重置 epoch 归属。
|
||||
self.regs = [JsValue::undefined(); 256];
|
||||
self.pc = 0;
|
||||
self.frames.clear();
|
||||
@@ -244,7 +244,7 @@ impl Vm {
|
||||
self.new_string_owned(s.to_string())
|
||||
}
|
||||
|
||||
/// Same as `new_string` but takes `String` by move, avoiding a clone.
|
||||
/// 同 `new_string`,但以 move 接收 `String`,避免一次克隆。
|
||||
pub fn new_string_owned(&mut self, s: String) -> JsValue {
|
||||
let len = s.len();
|
||||
let ptr = Box::into_raw(Box::new(JsString::new(s)));
|
||||
@@ -258,34 +258,30 @@ impl Vm {
|
||||
self.kernel_core.perm_interner().intern(s).0
|
||||
}
|
||||
|
||||
/// Intern a compile-time string literal as a permanent, process-lifetime
|
||||
/// `JsString` value shared across sessions. Source literals and RegExp
|
||||
/// source/flags recur (templated/repetitive code) and are immutable, so
|
||||
/// sharing them via `PermInterner` restores cross-session string reuse —
|
||||
/// without interning transient computed values, which stay session-heap
|
||||
/// (`new_string`) so they remain collectable.
|
||||
/// 把编译期字符串字面量 intern 为永久、进程生命周期内、跨 session 共享的
|
||||
/// `JsString` 值。源码字面量与 RegExp 的 source/flags 会反复出现(模板化/
|
||||
/// 重复代码)且不可变,经 `PermInterner` 共享可恢复跨 session 字符串复用——
|
||||
/// 不 intern 瞬态计算值(它们走 session 堆 `new_string`,保持可回收)。
|
||||
pub fn perm_string(&self, s: &str) -> JsValue {
|
||||
let id = self.kernel_core.perm_interner().intern(s).0;
|
||||
JsValue::perm_string(self.kernel_core.perm_interner().string_ptr(id))
|
||||
}
|
||||
|
||||
/// Drop all session-heap `JsString` values. Called only on full isolation reset
|
||||
/// (`full_reset` / `clear_full_reset_state`), where no surviving session object
|
||||
/// can reference them. The lighter `reset()` deliberately keeps them alive,
|
||||
/// mirroring session-object survival across evals.
|
||||
/// 释放全部 session 堆 `JsString`。仅在完全隔离重置
|
||||
/// (`full_reset` / `clear_full_reset_state`)时调用,此时没有存活的 session 对象
|
||||
/// 会引用它们。较轻量的 `reset()` 刻意保留它们,与 session 对象跨 eval 存活一致。
|
||||
fn free_session_string_heap_data(&mut self) {
|
||||
for ptr in self.gc_state.session_string_ptrs.drain(..) {
|
||||
// SAFETY: each ptr came from Box::into_raw(Box::new(JsString)) in new_string
|
||||
// and is dropped exactly once here.
|
||||
// SAFETY: 每个指针来自 new_string 中的 Box::into_raw(Box::new(JsString)),
|
||||
// 且只在这里恰好释放一次。
|
||||
unsafe {
|
||||
drop(Box::from_raw(ptr));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a function JsObject for a BytecodeFunc constant.
|
||||
/// When `is_arrow` is true, captures the current `this` (regs[254])
|
||||
/// for lexical this binding at call time.
|
||||
/// 为 BytecodeFunc 常量创建函数 JsObject。
|
||||
/// 当 `is_arrow` 为 true 时,捕获当前 `this`(regs[254]),供调用时词法 this 绑定。
|
||||
pub(crate) fn create_function_object(
|
||||
&mut self, sub_idx: u32, is_arrow: bool, is_class_constructor: bool, is_derived_constructor: bool,
|
||||
needs_home_object: bool,
|
||||
@@ -364,9 +360,9 @@ impl Vm {
|
||||
}
|
||||
}
|
||||
|
||||
/// Convert a module's immutable constant pool to `JsValue`s. Infallible — the pool holds only
|
||||
/// scalars + permanent-interner string values after CreateClosure/CreateRegExp pulled functions
|
||||
/// and regexes out of the pool. `&self` so it can run inside `OnceLock::get_or_init`.
|
||||
/// 把模块的不可变常量池转换为 `JsValue`。不可能失败——CreateClosure/CreateRegExp
|
||||
/// 已把函数与正则移出常量池,池中只剩标量 + 永久 intern 字符串值。`&self`
|
||||
/// 使其可运行于 `OnceLock::get_or_init` 内部。
|
||||
pub(crate) fn convert_immutables(&self, constants: &[Constant]) -> Vec<JsValue> {
|
||||
constants.iter().map(|c| self.convert_constant(c)).collect()
|
||||
}
|
||||
@@ -491,13 +487,13 @@ mod tests {
|
||||
#[test]
|
||||
fn immutables_cache_filled_once_per_module() {
|
||||
let mut vm = Vm::new();
|
||||
// `f` recurses (same sub-module entered 4x). Its immutables convert once via OnceLock.
|
||||
// `f` 递归(同一子模块进入 4 次),其不可变常量经 OnceLock 只转换一次。
|
||||
let result = run_source(&mut vm, "function f(n){ if(n<=0){ return 'done'; } return f(n-1); } f(3)");
|
||||
assert!(result.is_string());
|
||||
assert_eq!(vm.lookup_str(result).as_deref(), Some("done"));
|
||||
// Cache = top module + 1 sub-module (f); the sub-module slot was initialized by the calls.
|
||||
// 缓存 = 顶层模块 + 1 个子模块(f);子模块槽由这些调用初始化。
|
||||
assert_eq!(vm.immutables_cache.len(), 2);
|
||||
// Sub-module constants now use temp_immutables (avoiding cache index collision)
|
||||
// 子模块常量改走 temp_immutables(避免缓存下标冲突)
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
Reference in New Issue
Block a user