Files
OxideJS-New/crates/oxide_vm/src/vm_support.rs
T

1161 lines
56 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#![allow(clippy::arc_with_non_send_sync)]
use std::collections::VecDeque;
use std::sync::Arc;
use oxide_bytecode::module::Constant;
use crate::bindings;
use crate::vm::Vm;
use crate::vm_info;
use crate::vm_state::{GcState, IterState, ProfilingState, SymbolState};
use oxide_kernel::kernel::{KernelConfig, KernelCore, KernelSession};
use oxide_kernel::shape_forge::EMPTY_SHAPE_ID;
use oxide_types::mem::{Epoch, P};
use oxide_types::object::{JsObject, JsString, PropAttributes};
use oxide_types::value::JsValue;
impl Vm {
/// Cons(rope)节点保留的字节阈值:拼接总长 ≤ 该值时急切扁平为 Flat。
/// 小链的"链接 + 首次消费扁平化"双重分配高于直接拷贝,直接扁平更优;
/// 超过阈值后 O(n²) 拷贝成本超过节点开销,Cons 的 O(1) 链接才值得。
pub(crate) const CONS_FLATTEN_BYTES: usize = 128;
/// 以最小配置创建独立 VM:新建 `KernelCore` + `KernelSession` 并初始化内置对象。
pub fn new() -> Self {
let core = KernelCore::new(KernelConfig::minimal());
let mut session = KernelSession::new(&core);
bindings::init_kernel_builtins(&core, &mut session);
// 在 builtin 绑定之后取 id:绑定过程已 intern "length",此处命中缓存得到
// 非 0 的稳定 id(保持枚举层"id 0 哨兵"的既有约定,见 walk_own_keys)。
let length_si = core.perm_interner().intern("length").0;
let obj_proto = P::clone(&session.builtin_world().object_proto);
// 提前缓存执行期字符串 GC 初始水位(构造后 config 不再变化)。
let gc_threshold = core.config().session_gc_threshold;
let mut vm = Self {
regs: [JsValue::undefined(); 256],
pc: 0,
bytecode: Arc::default(),
immutables_cache: Vec::new(),
active_immutables: std::ptr::slice_from_raw_parts(std::ptr::null(), 0),
frames: smallvec::SmallVec::new(),
kernel_core: core,
session,
length_si,
epoch: Epoch::new(),
object_prototype: obj_proto,
generator_proto: P::new(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::null())),
generator_function_proto: P::new(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::null())),
promise_constructor: P::new(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::null())),
promise_proto: P::new(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::null())),
aggregate_error_constructor: P::new(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::null())),
aggregate_error_proto: P::new(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::null())),
async_function_proto: P::new(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::null())),
async_generator_proto: P::new(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::null())),
async_generator_function_proto: P::new(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::null())),
job_queue: VecDeque::new(),
math_rng_state: 0,
sub_modules: Arc::new(Vec::new()),
saved_bytecode_stack: Vec::new(),
saved_immutables_stack: Vec::new(),
save_stack: Vec::new(),
spill_stack: Vec::new(),
native_overflow_base: 0,
native_overflow_count: 0,
try_stack: Vec::new(),
exception_value: None,
last_uncaught_value: None,
pending_exception: None,
pending_error_kind: None,
pending_completion: None,
root_reg_limit: 0,
active_reg_limit: 0,
native_call_depth: 0,
inline_args_base: 0,
inline_args_count: 0,
accessor_frame_target_reg: None,
inline_callee: None,
inline_reg_pool: None,
generator_suspended: None,
delegated_iterator: None,
generator_dispatch: false,
generator_init_step: false,
generator_body_started: false,
async_context: None,
async_suspended: false,
async_dispatch: false,
construct_dispatch: false,
async_gen_context: None,
async_gen_dispatch: false,
async_gen_suspended: false,
gc_state: GcState {
session_epoch: bumpalo::Bump::new(),
session_gc: crate::session_gc::SessionGc::new(),
epoch_object_ptrs: Vec::new(),
session_object_ptrs: Vec::new(),
session_string_ptrs: Vec::new(),
session_bigint_ptrs: std::cell::RefCell::new(Vec::new()),
session_cell_ptrs: std::cell::RefCell::new(Vec::new()),
session_bytes_allocated: 0,
string_gc_watermark: gc_threshold,
gc_threshold_cached: gc_threshold,
gc_watermark: gc_threshold,
forwarding: std::collections::HashMap::with_hasher(rustc_hash::FxBuildHasher),
},
symbols: SymbolState {
symbol_counter: 0,
symbol_descriptions: Vec::new(),
symbol_registry: std::collections::HashMap::new(),
},
iters: IterState {
for_in_iters: Vec::new(),
for_of_iters: Vec::new(),
},
profiling: ProfilingState {
ic_hits: std::cell::Cell::new(0),
ic_misses: std::cell::Cell::new(0),
instruction_count: 0,
},
cell_stack: Vec::new(),
};
vm.init_generator_intrinsics();
vm.init_promise_intrinsics();
vm.init_async_intrinsics();
vm.init_async_generator_intrinsics();
// Promise 全局绑定发生在快照采集之后,重录快照避免首次 full_reset 误判脏。
vm.session.record_snapshot();
vm_info!("Vm created");
vm
}
/// 复用共享 `KernelCore` 创建 VM(VM 池路径),共享 intern/shape/code 缓存。
pub fn with_kernel_core(core: Arc<KernelCore>) -> Self {
let mut session = KernelSession::new(&core);
bindings::init_kernel_builtins(&core, &mut session);
// 在 builtin 绑定之后取 id:绑定过程已 intern "length",此处命中缓存得到
// 非 0 的稳定 id(保持枚举层"id 0 哨兵"的既有约定,见 walk_own_keys)。
let length_si = core.perm_interner().intern("length").0;
let obj_proto = P::clone(&session.builtin_world().object_proto);
// 提前缓存执行期字符串 GC 初始水位(构造后 config 不再变化)。
let gc_threshold = core.config().session_gc_threshold;
let mut vm = Self {
regs: [JsValue::undefined(); 256],
pc: 0,
bytecode: Arc::default(),
immutables_cache: Vec::new(),
active_immutables: std::ptr::slice_from_raw_parts(std::ptr::null(), 0),
frames: smallvec::SmallVec::new(),
kernel_core: core,
session,
length_si,
epoch: Epoch::new(),
object_prototype: obj_proto,
generator_proto: P::new(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::null())),
generator_function_proto: P::new(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::null())),
promise_constructor: P::new(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::null())),
promise_proto: P::new(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::null())),
aggregate_error_constructor: P::new(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::null())),
aggregate_error_proto: P::new(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::null())),
async_function_proto: P::new(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::null())),
async_generator_proto: P::new(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::null())),
async_generator_function_proto: P::new(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::null())),
job_queue: VecDeque::new(),
math_rng_state: 0,
sub_modules: Arc::new(Vec::new()),
saved_bytecode_stack: Vec::new(),
saved_immutables_stack: Vec::new(),
save_stack: Vec::new(),
spill_stack: Vec::new(),
native_overflow_base: 0,
native_overflow_count: 0,
try_stack: Vec::new(),
exception_value: None,
last_uncaught_value: None,
pending_exception: None,
pending_error_kind: None,
pending_completion: None,
root_reg_limit: 0,
active_reg_limit: 0,
native_call_depth: 0,
inline_args_base: 0,
inline_args_count: 0,
accessor_frame_target_reg: None,
inline_callee: None,
inline_reg_pool: None,
generator_suspended: None,
delegated_iterator: None,
generator_dispatch: false,
generator_init_step: false,
generator_body_started: false,
async_context: None,
async_suspended: false,
async_dispatch: false,
construct_dispatch: false,
async_gen_context: None,
async_gen_dispatch: false,
async_gen_suspended: false,
gc_state: GcState {
session_epoch: bumpalo::Bump::new(),
session_gc: crate::session_gc::SessionGc::new(),
epoch_object_ptrs: Vec::new(),
session_object_ptrs: Vec::new(),
session_string_ptrs: Vec::new(),
session_bigint_ptrs: std::cell::RefCell::new(Vec::new()),
session_cell_ptrs: std::cell::RefCell::new(Vec::new()),
session_bytes_allocated: 0,
string_gc_watermark: gc_threshold,
gc_threshold_cached: gc_threshold,
gc_watermark: gc_threshold,
forwarding: std::collections::HashMap::with_hasher(rustc_hash::FxBuildHasher),
},
symbols: SymbolState {
symbol_counter: 0,
symbol_descriptions: Vec::new(),
symbol_registry: std::collections::HashMap::new(),
},
iters: IterState {
for_in_iters: Vec::new(),
for_of_iters: Vec::new(),
},
profiling: ProfilingState {
ic_hits: std::cell::Cell::new(0),
ic_misses: std::cell::Cell::new(0),
instruction_count: 0,
},
cell_stack: Vec::new(),
};
vm.init_generator_intrinsics();
vm.init_promise_intrinsics();
vm.init_async_intrinsics();
vm.init_async_generator_intrinsics();
// Promise 全局绑定发生在快照采集之后,重录快照避免首次 full_reset 误判脏。
vm.session.record_snapshot();
vm_info!("Vm created (pool)");
vm
}
/// 初始化/重建生成器内建对象(`%GeneratorPrototype%` 与 `%GeneratorFunction.prototype%`)。
///
/// 在 VM 创建与 `full_reset`(session 重建)后调用——原型继承自 session 的
/// Object/Function 原型,session 重建后须重挂。
pub(crate) fn init_generator_intrinsics(&mut self) {
crate::generator::init_generator_intrinsics(self);
}
/// 初始化/重建异步函数内建对象(`%AsyncFunction.prototype%`)。
///
/// 在 VM 创建与 `full_reset`(session 重建)后调用。
pub(crate) fn init_async_intrinsics(&mut self) {
crate::async_func::init_async_intrinsics(self);
}
/// 初始化/重建异步生成器内建对象(`%AsyncGeneratorPrototype%` 与
/// `%AsyncGeneratorFunction.prototype%`)。
///
/// 在 VM 创建与 `full_reset`(session 重建)后调用。
pub(crate) fn init_async_generator_intrinsics(&mut self) {
crate::async_generator::init_async_generator_intrinsics(self);
}
/// 全量隔离重置:仅重建被污染的内置对象与 global,并清空所有执行状态与内存。
///
/// 用于在多次 JS 执行之间达到完全隔离:session 内未被污染的 builtin 保留原指针。
pub fn full_reset(&mut self) {
// session 对象只能来自用户写 + promote,跨 full_reset 若保留 global 会悬垂:
// 覆盖既有 global 槽的写入不递增 generation(脏检测依赖 generation 对比),
// 此处强制 bump 使带 session 对象时 global 必然重建。
if !self.gc_state.session_object_ptrs.is_empty() {
let global_ptr = self.session.global_object().as_ptr() as *mut JsObject;
unsafe { &mut *global_ptr }.bump_generation();
}
let dirty = self.session.selective_reset(&self.kernel_core);
if dirty.any_builtin_dirty() {
bindings::rebind_dirty_builtins(&self.kernel_core, &mut self.session, Some(&dirty));
}
if dirty.global {
let global_ptr = self.session.global_object().as_ptr() as *mut JsObject;
let global = unsafe { &mut *global_ptr };
bindings::bind_global_builtin_slots(&self.kernel_core, &self.session, global);
}
self.object_prototype = P::clone(&self.session.builtin_world().object_proto);
self.init_generator_intrinsics();
self.init_promise_intrinsics();
self.init_async_intrinsics();
self.init_async_generator_intrinsics();
// 快照须在 Promise 全局绑定之后采集:绑定会修改 global 世代。
self.session.record_snapshot();
self.clear_full_reset_state();
vm_info!("full_reset completed");
}
/// 旧版全量重置:总是丢弃并重建整个 session 与内置对象(benchmark 专用)。
#[doc(hidden)]
pub fn full_reset_legacy_for_bench(&mut self) {
self.session = KernelSession::new(&self.kernel_core);
bindings::init_kernel_builtins(&self.kernel_core, &mut self.session);
self.object_prototype = P::clone(&self.session.builtin_world().object_proto);
self.init_generator_intrinsics();
self.init_promise_intrinsics();
self.init_async_intrinsics();
self.init_async_generator_intrinsics();
self.session.record_snapshot();
self.clear_full_reset_state();
}
fn clear_full_reset_state(&mut self) {
self.clear_execution_state();
self.bytecode = Arc::default();
self.immutables_cache.clear();
self.active_immutables = std::ptr::slice_from_raw_parts(std::ptr::null(), 0);
self.teardown_session_heap_data();
self.epoch.reset();
self.gc_state.epoch_object_ptrs.clear();
self.gc_state.session_epoch.reset();
self.gc_state.session_bytes_allocated = 0;
self.gc_state.string_gc_watermark = self.kernel_core.config().session_gc_threshold;
self.gc_state.session_gc = crate::session_gc::SessionGc::new();
self.symbols.reset();
self.root_reg_limit = 0;
self.active_reg_limit = 0;
}
/// 释放全部 session 堆数据:epoch 对象堆数据 + session 对象堆数据 + session 串 +
/// BigInt box + upvalue cell box。
///
/// 供 `full_reset` 与 `Drop` 共用——对象本体(bumpalo arena / epoch bump)由调用方
/// 重置,本函数只释放手工管理的 Box 指针(属性向量、各原生盒、串、BigInt、cell)。
/// 原生盒在 GC 搬移/晋升时已深拷贝为单所有权,此处恰好释放一次。
pub(crate) fn teardown_session_heap_data(&mut self) {
self.free_epoch_object_heap_data();
for ptr in self.gc_state.session_object_ptrs.drain(..) {
crate::session_gc::SessionGc::drop_object_heap_data(ptr, true);
}
self.free_session_string_heap_data();
self.free_session_bigint_heap_data();
self.gc_state.free_cells();
}
fn free_epoch_object_heap_data(&mut self) {
let mut freed = 0u64;
for ptr in self.gc_state.epoch_object_ptrs.drain(..) {
freed += crate::session_gc::SessionGc::drop_object_heap_data(ptr, false);
}
if freed > 0 {
self.gc_state.session_gc.total_bytes_freed =
self.gc_state.session_gc.total_bytes_freed.saturating_add(freed);
self.gc_state.session_gc.last_collection_bytes_freed = freed;
}
}
pub(crate) fn clear_execution_state(&mut self) {
// 重置契约:
// - 清空寄存器文件、pc、帧/迭代器栈、保存的执行栈、try 处理器、
// 待处理异常与 native 调用深度。
// - 保留 kernel 共享状态不变。
// - `reset()` 额外清空 bytecode/constants 并重置 epoch 归属。
self.regs = [JsValue::undefined(); 256];
self.pc = 0;
self.frames.clear();
self.iters.reset();
self.saved_bytecode_stack.clear();
self.saved_immutables_stack.clear();
self.save_stack.clear();
self.spill_stack.clear();
self.native_overflow_base = 0;
self.native_overflow_count = 0;
self.cell_stack.clear();
self.try_stack.clear();
self.exception_value = None;
self.pending_exception = None;
self.pending_error_kind = None;
self.pending_completion = None;
self.generator_suspended = None;
self.delegated_iterator = None;
self.generator_dispatch = false;
self.generator_init_step = false;
self.generator_body_started = false;
self.async_context = None;
self.async_suspended = false;
self.async_dispatch = false;
self.async_gen_context = None;
self.async_gen_dispatch = false;
self.async_gen_suspended = false;
self.native_call_depth = 0;
// inline 窗口缓冲池内容为已废弃快照,跨 run/reset 不保留。
self.inline_reg_pool = None;
// 微任务队列是执行期状态:跨 run 不保留。
self.job_queue.clear();
}
/// 轻量重置:清空执行状态并回收 epoch 内存,但保留 session 字符串与 builtin。
pub fn reset(&mut self) {
self.clear_execution_state();
self.maybe_collect_session_gc();
self.bytecode = Arc::default();
self.immutables_cache.clear();
self.active_immutables = std::ptr::slice_from_raw_parts(std::ptr::null(), 0);
self.free_epoch_object_heap_data();
self.epoch.reset();
self.gc_state.epoch_object_ptrs.clear();
self.root_reg_limit = 0;
self.active_reg_limit = 0;
}
/// 分配一个可被 session GC 回收的字符串 `JsValue`(session-heap 字符串)。
pub fn new_string(&mut self, s: &str) -> JsValue {
self.new_string_owned(s.to_string())
}
/// 同 `new_string`,但以 move 接收 `String`,避免一次克隆。
///
/// # 副作用
/// - 累计 session 字符串字节账目;回收不在分配点触发(见注意事项)。
///
/// # 注意事项
/// - 分配点不触发 GC:builtin 函数栈上的局部 `JsValue` 与构造中对象不在
/// mark 根清单内,分配前触发会释放"仅存于局部/构造中"的活串(悬垂)。
/// 回收统一在 dispatch 指令边界检查水位触发——此时 builtin 局部值已落地
/// 为执行根,任何分配点的局部持有跨分配点均安全。
/// - 热路径仅 1 次字节账目累加,无阈值读取与分支。
pub fn new_string_owned(&mut self, s: String) -> JsValue {
let len = s.len();
let ptr = Box::into_raw(Box::new(JsString::new(s)));
self.register_session_string(ptr, len)
}
/// 登记一个 session 字符串并记账(`new_string_owned` 与 `new_cons_string`
/// 共用),返回字符串值。
///
/// # 步骤
/// 1. 登记指针到 session 字符串表。
/// 2. 记账 `size_of::<JsString>() + bytes`。
///
/// # 注意事项
/// - 本方法**不触发**回收:执行期字符串 GC 统一在 dispatch 指令边界检查
/// 水位触发(此时 builtin 局部值已落地为执行根,分配点触发会误释放
/// 仅存于局部/构造中的活串)。
fn register_session_string(&mut self, ptr: *mut JsString, bytes: usize) -> JsValue {
self.gc_state.session_string_ptrs.push(ptr);
self.gc_state.session_bytes_allocated += std::mem::size_of::<JsString>() + bytes;
JsValue::string(ptr)
}
/// 分配 Cons(rope)节点:左右子节点 O(1) 链接,不拷贝文本。
///
/// # 边界与前提
/// - `left`/`right` 必须均为字符串值;仅在 `+`/`+=` 的 `concat_strings`
/// 接线点调用(CONCAT_N 保持急切扁平,不经此路径)。
///
/// # 副作用
/// - 登记节点到 session 字符串表(账目 = `size_of::<JsString>() + byte_len`)。
///
/// # 注意事项
/// - 调用方须保证 `left`/`right` 在调用期间存活:子节点(coerce 新鲜字符串 /
/// 新建叶子)要么已是执行根,要么在写入根前不被任何回收点触达——当前
/// 回收点仅在指令边界,链接结果写寄存器先于下一次检查,天然满足。
/// - 调用方(`concat_strings`)已按字节阈值过滤:仅大链(总长 >
/// [`Self::CONS_FLATTEN_BYTES`])进入本方法,小链由其急切扁平。
#[inline]
pub fn new_cons_string(&mut self, left: JsValue, right: JsValue) -> JsValue {
debug_assert!(left.is_string() && right.is_string(), "new_cons_string 只接收字符串操作数");
let left_ptr = left.as_string_ptr_mut();
let right_ptr = right.as_string_ptr_mut();
// SAFETY: 两指针指向存活的 JsString(调用方保证),len() 各为 O(1)。
let byte_len = unsafe { (*left_ptr).len() + (*right_ptr).len() };
// SAFETY: new_cons 的调用方(本函数)负责保证子节点随节点存活。
let ptr = Box::into_raw(Box::new(unsafe { JsString::new_cons(left_ptr, right_ptr) }));
self.register_session_string(ptr, byte_len)
}
/// 把字符串 intern 为永久 key id(属性名/方法名),进程生命周期内稳定。
pub fn intern_key(&self, s: &str) -> u32 {
self.kernel_core.perm_interner().intern(s).0
}
/// 把编译期字符串字面量 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))
}
/// 释放全部 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: 每个指针来自 new_string/new_cons_string 的 Box::into_raw,
// 且只在这里(或 sweep)恰好释放一次;内部连带释放 rope 扁平化产物。
unsafe {
crate::session_gc::SessionGc::drop_session_string_box(ptr);
}
}
}
/// 为 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,
) -> JsValue {
// 生成器函数对象:原型为 %GeneratorFunction.prototype%(constructor 链解析到
// "GeneratorFunction"),且不像普通函数那样拥有 `prototype` 属性。
let is_generator = self.sub_modules.get(sub_idx as usize).map(|m| m.is_generator).unwrap_or(false);
let is_async = self.sub_modules.get(sub_idx as usize).map(|m| m.is_async).unwrap_or(false);
// 异步生成器(`async function*`)函数对象:原型为 %AsyncGeneratorFunction.prototype%。
let is_async_generator = is_generator && is_async;
let proto_val = if is_async_generator {
JsValue::from_js_object(self.async_generator_function_proto.as_ptr() as *mut JsObject)
} else if is_generator {
JsValue::from_js_object(self.generator_function_proto.as_ptr() as *mut JsObject)
} else if is_async {
JsValue::from_js_object(self.async_function_proto.as_ptr() as *mut JsObject)
} else {
JsValue::from_js_object(self.session.builtin_world().function_proto.as_ptr() as *mut JsObject)
};
let mut obj = JsObject::new_empty(EMPTY_SHAPE_ID, proto_val);
obj.set_function(true);
obj.set_sub_module_index(sub_idx);
obj.set_class_constructor(is_class_constructor);
obj.set_derived_constructor(is_derived_constructor);
let _ = needs_home_object;
if is_arrow {
obj.set_arrow(true);
obj.set_captured_this(self.regs[254]);
}
let obj_ptr = self.alloc_object(obj);
let func_val = JsValue::object(obj_ptr as *mut u8);
if !is_arrow {
// 原型对象自身的 [[Prototype]]:生成器为 %GeneratorPrototype%,普通函数为 Object.prototype。
let proto_of_proto = if is_async_generator {
JsValue::from_js_object(self.async_generator_proto.as_ptr() as *mut JsObject)
} else if is_generator {
JsValue::from_js_object(self.generator_proto.as_ptr() as *mut JsObject)
} else {
JsValue::from_js_object(self.session.builtin_world().object_proto.as_ptr() as *mut JsObject)
};
let prototype_obj = self.epoch.alloc(JsObject::new_empty(EMPTY_SHAPE_ID, proto_of_proto));
self.gc_state.track_epoch_object(prototype_obj);
let prototype_val = JsValue::from_js_object(prototype_obj);
if !is_generator {
// 普通函数:prototype 对象带 constructor 指回函数。
let constructor_si = self.kernel_core.perm_interner().intern("constructor").0;
let constructor_shape = self.kernel_core.shape_forge().make_shape(EMPTY_SHAPE_ID, constructor_si);
let prototype = unsafe { &mut *prototype_obj };
prototype.set_shape_id(constructor_shape);
let constructor_pos = prototype.push_prop(func_val);
prototype.set_data_meta(constructor_pos, PropAttributes::new(true, false, true));
prototype.bump_generation();
}
// 函数自身 `prototype` 属性:生成器 writable:true / enumerable:false / configurable:false。
let prototype_si = self.kernel_core.perm_interner().intern("prototype").0;
let func = unsafe { &mut *obj_ptr };
let prototype_shape = self.kernel_core.shape_forge().make_shape(func.shape_id(), prototype_si);
func.set_shape_id(prototype_shape);
let prototype_pos = func.push_prop(prototype_val);
if is_generator || is_async_generator {
func.set_data_meta(prototype_pos, PropAttributes::new(true, false, false));
}
func.bump_generation();
}
func_val
}
pub(crate) fn error_text(&self, val: JsValue) -> String {
if let Some(s) = self.lookup_str(val) {
return s;
}
if val.is_object() {
let obj = unsafe { &*val.as_js_object_ptr() };
let name_si = self.kernel_core.perm_interner().intern("name").0;
let message_si = self.kernel_core.perm_interner().intern("message").0;
let name = self
.resolve_property(obj, name_si)
.and_then(|v| self.lookup_str(v))
.unwrap_or_else(|| "Error".to_string());
let message = self
.resolve_property(obj, message_si)
.and_then(|v| self.lookup_str(v))
.unwrap_or_default();
return crate::vm::format_error_message(&name, &message);
}
format!("{val}")
}
/// 分配一个 BigInt 值:把 `i128` 堆分配为 box 并返回携带指针的 `JsValue`。
///
/// box 指针登记进 `gc_state.session_bigint_ptrs`,在 `full_reset` 统一释放。
/// `&self` 使 `convert_immutables`(常量池 → JsValue)也能分配。
pub fn new_bigint(&self, v: num_bigint::BigInt) -> JsValue {
let ptr = Box::into_raw(Box::new(v));
self.gc_state.session_bigint_ptrs.borrow_mut().push(ptr);
JsValue::bigint(ptr)
}
/// 读取 BigInt 值;调用方须保证 `val.is_bigint()`。
pub fn bigint_value(&self, val: JsValue) -> &num_bigint::BigInt {
// SAFETY: bigint 指针由 new_bigint 经 Box::into_raw 产生,存活至 full_reset。
unsafe { &*val.as_bigint_ptr() }
}
/// 释放全部 session 堆 BigInt box。仅在完全隔离重置(`full_reset`)时调用,
/// 此时没有存活的 session 对象/寄存器会引用它们。
fn free_session_bigint_heap_data(&mut self) {
for ptr in self.gc_state.session_bigint_ptrs.borrow_mut().drain(..) {
// SAFETY: 每个指针来自 new_bigint 的 Box::into_raw(Box::new(BigInt)),
// 且只在这里恰好释放一次。
unsafe {
drop(Box::from_raw(ptr));
}
}
}
fn convert_constant(&self, constant: &Constant) -> JsValue {
match constant {
Constant::Number(v) => JsValue::float(*v),
Constant::Int(v) => JsValue::int(*v),
Constant::BigInt(v) => self.new_bigint(v.clone()),
Constant::String(s) => self.perm_string(s),
Constant::Boolean(b) => JsValue::bool(*b),
Constant::Null => JsValue::null(),
Constant::Undefined => JsValue::undefined(),
}
}
/// 把模块的不可变常量池转换为 `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()
}
}
#[cfg(test)]
mod tests {
use super::*;
fn global_prop(vm: &Vm, name: &str) -> JsValue {
global_prop_opt(vm, name).expect("global slot should exist")
}
fn global_prop_opt(vm: &Vm, name: &str) -> Option<JsValue> {
let global = vm.session.global_object();
let si = vm.kernel_core.perm_interner().intern(name).0;
vm.kernel_core
.shape_forge()
.lookup_position(global.shape_id(), si)
.map(|pos| global.get_prop_at(pos))
}
fn run_source(vm: &mut Vm, source: &str) -> JsValue {
let allocator = oxide_parser::Allocator::default();
let program = oxide_parser::parse(&allocator, source).expect("parse failed");
let module = oxide_compiler::compiler::Compiler::new()
.compile(&program)
.expect("compile failed");
vm.run(&module).expect("vm run failed")
}
#[test]
fn full_reset_with_session_objects_forces_global_rebuild() {
let mut vm = Vm::new();
// 池路径场景:`globalThis.Array = {}` 覆盖既有 global 槽(不递增 generation),
// 新值 `{}` 经 promote 进入 session——global 保留时该指针将悬垂。
let _ = run_source(&mut vm, "globalThis.Array = {}; 0");
assert!(!vm.gc_state.session_object_ptrs.is_empty(), "覆盖写应触发 promote 进入 session");
let old_global = vm.session.global_object.as_ptr();
vm.full_reset();
// global 必须重建:旧 global 与其 session 对象随 epoch 释放,Array 恢复内置构造器。
assert!(!std::ptr::eq(old_global, vm.session.global_object.as_ptr()));
assert!(std::ptr::eq(
global_prop(&vm, "Array").as_js_object_ptr(),
vm.session.builtin_world().array_constructor.as_ptr() as *mut JsObject
));
assert!(!vm.session.is_dirty_since_snapshot());
}
fn vm_with_low_threshold() -> Vm {
let mut cfg = KernelConfig::minimal();
cfg.set_session_gc_threshold(1);
Vm::with_kernel_core(KernelCore::new(cfg))
}
/// 直接恢复生成器一步:等价 `it.next()`,但避免跨 run(sub_modules 重建会使
/// 挂起帧失效)——回归聚焦 GC 搬移后的状态盒有效性。
fn resume_one_step(vm: &mut Vm, gen: JsValue) -> JsValue {
match vm.resume_generator(gen, crate::generator::GeneratorResumeMode::Next(JsValue::undefined())) {
Ok(crate::generator::GeneratorStep::Suspended { value }) => value,
Ok(crate::generator::GeneratorStep::Completed { value }) => value,
Ok(other) => panic!(
"unexpected step: {:?}",
match other {
crate::generator::GeneratorStep::Thrown { value } => format!("Thrown({value})"),
crate::generator::GeneratorStep::SuspendedRaw { value } => format!("SuspendedRaw({value})"),
_ => String::new(),
}
),
Err(e) => panic!("resume failed: {e}"),
}
}
#[test]
fn generator_survives_object_sweep_and_resumes() {
let mut vm = vm_with_low_threshold();
let _ = run_source(&mut vm, "function* g(){ yield 1; yield 2; } globalThis.it = g(); globalThis.it.next(); 0");
// 直接触发完整收集(保留执行上下文):存活生成器克隆进新 arena,
// 状态盒深拷贝为新 Box(reset 会清空模块表使恢复不可行,走收集入口等价验证)。
vm.maybe_collect_session_gc();
assert!(vm.session_gc_stats().total_collections > 0, "应触发对象收集");
// 从 global 取 sweep 重写后的生成器(sub_modules/immutables 未重建,可恢复)。
let it = global_prop(&vm, "it");
assert_eq!(resume_one_step(&mut vm, it), JsValue::int(2), "sweep 后应恢复第二次 yield");
}
#[test]
fn generator_captured_upvalue_survives_sweep() {
let mut vm = vm_with_low_threshold();
let _ = run_source(
&mut vm,
"var x = 0; function* g(){ x++; yield x; x++; yield x; } globalThis.it = g(); globalThis.it.next(); 0",
);
vm.maybe_collect_session_gc();
assert!(vm.session_gc_stats().total_collections > 0, "应触发对象收集");
// 挂起帧 cell_stack 与闭包 upvalues 中的 cell 独立堆分配(地址稳定),
// 恢复后继续读写捕获变量。
let it = global_prop(&vm, "it");
assert_eq!(resume_one_step(&mut vm, it), JsValue::int(2), "sweep 后应恢复捕获变量读写");
}
#[test]
fn generator_promoted_clone_owns_independent_state_box() {
let mut vm = Vm::new();
// `var it = g(); it.next(); it`:it 为 epoch 生成器对象(未逃逸不 promote)。
let it = run_source(&mut vm, "function* g(){ yield 1; yield 2; } var it = g(); it.next(); it");
assert!(it.is_object());
let epoch_ptr = it.as_js_object_ptr();
let epoch_box = unsafe { (*epoch_ptr).native_data() };
// 手动 promote:克隆应深拷贝状态盒(新 Box),与源盒互不共享。
let promoted = vm.promote_object(epoch_ptr);
assert!(!std::ptr::eq(promoted, epoch_ptr));
let promoted_box = unsafe { (*promoted).native_data() };
assert!(!std::ptr::eq(epoch_box, promoted_box), "promote 应深拷贝生成器状态盒");
// 模拟 full_reset 的 epoch 侧释放:源对象与其状态盒随 epoch 回收,
// 并从追踪表移除登记(克隆的后续回收仍由 VM 统一处理)。
let _ = crate::session_gc::SessionGc::drop_object_heap_data(epoch_ptr, false);
vm.gc_state.epoch_object_ptrs.retain(|&p| !std::ptr::eq(p, epoch_ptr));
// 克隆直接恢复执行:读新盒中的挂起状态,不得悬垂。
assert_eq!(resume_one_step(&mut vm, JsValue::from_js_object(promoted)), JsValue::int(2));
}
#[test]
fn full_reset_clean_keeps_session_objects() {
let mut vm = Vm::new();
let world_ptr = Arc::as_ptr(&vm.session.builtin_world);
let global_ptr = vm.session.global_object.as_ptr();
let object_proto_ptr = vm.session.builtin_world().object_proto.as_ptr();
vm.full_reset();
assert!(std::ptr::eq(world_ptr, Arc::as_ptr(&vm.session.builtin_world)));
assert!(std::ptr::eq(global_ptr, vm.session.global_object.as_ptr()));
assert!(std::ptr::eq(object_proto_ptr, vm.session.builtin_world().object_proto.as_ptr()));
assert!(!vm.session.is_dirty_since_snapshot());
}
#[test]
fn full_reset_global_dirty_rebuilds_global_and_restores_slots() {
let mut vm = Vm::new();
let world_ptr = Arc::as_ptr(&vm.session.builtin_world);
let global_ptr = vm.session.global_object.as_ptr();
let global = unsafe { &mut *(vm.session.global_object.as_ptr() as *mut JsObject) };
bindings::bind_global_value(&vm.kernel_core, global, "userGlobal", JsValue::int(99));
unsafe { &mut *(vm.session.global_object.as_ptr() as *mut JsObject) }.bump_generation();
vm.full_reset();
assert!(std::ptr::eq(world_ptr, Arc::as_ptr(&vm.session.builtin_world)));
assert!(!std::ptr::eq(global_ptr, vm.session.global_object.as_ptr()));
assert!(global_prop_opt(&vm, "userGlobal").is_none());
assert!(std::ptr::eq(
global_prop(&vm, "Array").as_js_object_ptr(),
vm.session.builtin_world().array_constructor.as_ptr() as *mut JsObject
));
assert!(std::ptr::eq(
global_prop(&vm, "globalThis").as_js_object_ptr(),
vm.session.global_object.as_ptr() as *mut JsObject
));
assert!(!vm.session.is_dirty_since_snapshot());
}
#[test]
fn full_reset_dirty_builtin_rebinds_global_slot() {
let mut vm = Vm::new();
let old_object_proto = vm.session.builtin_world().object_proto.as_ptr();
let old_array_proto = vm.session.builtin_world().array_proto.as_ptr();
unsafe { &mut *(old_array_proto as *mut JsObject) }.bump_generation();
vm.full_reset();
assert!(std::ptr::eq(old_object_proto, vm.session.builtin_world().object_proto.as_ptr()));
assert!(!std::ptr::eq(old_array_proto, vm.session.builtin_world().array_proto.as_ptr()));
assert!(std::ptr::eq(
global_prop(&vm, "Array").as_js_object_ptr(),
vm.session.builtin_world().array_constructor.as_ptr() as *mut JsObject
));
let constructor_si = vm.kernel_core.perm_interner().intern("constructor").0;
let array_proto = &*vm.session.builtin_world().array_proto;
let constructor = vm
.resolve_property(array_proto, constructor_si)
.expect("Array.prototype.constructor");
assert!(std::ptr::eq(
constructor.as_js_object_ptr(),
vm.session.builtin_world().array_constructor.as_ptr() as *mut JsObject
));
assert!(!vm.session.is_dirty_since_snapshot());
}
#[test]
fn full_reset_dirty_function_keeps_call_working() {
let mut vm = Vm::new();
let function_proto = vm.session.builtin_world().function_proto.as_ptr();
unsafe { &mut *(function_proto as *mut JsObject) }.bump_generation();
vm.full_reset();
// Function 原型家族重建后,未重建家族(array/string/object/map 等)的方法
// wrapper 是跨重置存活对象,其原型链必须仍能解析出 call/apply/bind:
// 任何一条路径失效都说明 wrapper 原型指向了已释放的旧 Function 原型。
assert_eq!(run_source(&mut vm, "Array.prototype.push.call([1], 2)"), JsValue::int(2));
assert_eq!(run_source(&mut vm, "Array.prototype.push.apply([], [1, 2, 3])"), JsValue::int(3));
let replaced = run_source(&mut vm, "String.prototype.replace.call('a', 'a', 'b')");
assert_eq!(vm.lookup_str(replaced).as_deref(), Some("b"));
let has = run_source(&mut vm, "Object.prototype.hasOwnProperty.call({x: 1}, 'x')");
assert_eq!(has, JsValue::bool(true));
let fixed = run_source(&mut vm, "Number.prototype.toFixed.call(1.5, 1)");
assert_eq!(vm.lookup_str(fixed).as_deref(), Some("1.5"));
let in_map = run_source(&mut vm, "Map.prototype.has.call(new Map([[1, 2]]), 1)");
assert_eq!(in_map, JsValue::bool(true));
let mapped = run_source(&mut vm, "Array.prototype.map.call([1, 2], function(x){ return x + 1; }).join(',')");
assert_eq!(vm.lookup_str(mapped).as_deref(), Some("2,3"));
assert!(!vm.session.is_dirty_since_snapshot());
}
#[test]
fn session_epoch_survives_reset() {
let mut vm = Vm::new();
let session_ptr = vm.gc_state.session_epoch.alloc(123i32) as *mut i32;
vm.reset();
assert!(unsafe { *session_ptr } == 123);
}
#[test]
fn immutables_cache_filled_once_per_module() {
let mut vm = Vm::new();
// `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"));
// 缓存 = 顶层模块 + 1 个子模块(f);子模块槽由这些调用初始化。
assert_eq!(vm.immutables_cache.len(), 2);
// 子模块常量改走 temp_immutables(避免缓存下标冲突)
}
#[test]
fn dynamic_function_basic_arity() {
let mut vm = Vm::new();
// 与等价静态函数返回值逐位一致(引擎函数调用统一产 Double 数值)。
let expected = run_source(&mut vm, "function f(a,b){return a+b} f(3,4)");
let result = run_source(&mut vm, "new Function('a','b','return a+b')(3,4)");
assert_eq!(result, expected);
}
#[test]
fn dynamic_function_called_without_new() {
let mut vm = Vm::new();
let expected = run_source(&mut vm, "function f(a,b){return a*b} f(6,7)");
let result = run_source(&mut vm, "Function('a','b','return a*b')(6,7)");
assert_eq!(result, expected);
}
#[test]
fn dynamic_function_empty_body_returns_undefined() {
let mut vm = Vm::new();
assert!(run_source(&mut vm, "Function()()").is_undefined());
}
#[test]
fn dynamic_function_syntax_error_throws_syntax_error() {
let mut vm = Vm::new();
let result = run_source(&mut vm, "try{new Function('return {{')}catch(e){e.name}");
assert!(result.is_string());
assert_eq!(vm.lookup_str(result).as_deref(), Some("SyntaxError"));
}
#[test]
fn dynamic_function_nested_closure_renumbering() {
let mut vm = Vm::new();
// 匿名函数体声明嵌套函数 g,返回值是引用 g 的闭包:验证子树 flat_id 重编号
// 与 CREATE_CLOSURE imm16 重写后嵌套调用仍指向正确的子模块。
let expected = run_source(
&mut vm,
"function outer(){var g=function(n){return n*2}; return function(){return g(21)}} outer()()",
);
let result = run_source(
&mut vm,
"new Function('var g=function(n){return n*2}; return function(){return g(21)}')()()",
);
assert_eq!(result, expected);
}
#[test]
fn dynamic_function_multiple_in_one_run() {
let mut vm = Vm::new();
// 同一 run 内连续创建多个动态函数:验证 base 偏移累计正确。
let expected = run_source(
&mut vm,
"function f1(){return 1} function f2(){return 2} function f3(a){return a*3} f1()+f2()+f3(4)",
);
let result = run_source(
&mut vm,
"new Function('return 1')() + new Function('return 2')() + new Function('a','return a*3')(4)",
);
assert_eq!(result, expected);
}
#[test]
fn dynamic_function_name_and_length() {
let mut vm = Vm::new();
let name = run_source(&mut vm, "var f=new Function('a','b','return a'); f.name");
assert!(name.is_string());
assert_eq!(vm.lookup_str(name).as_deref(), Some("anonymous"));
let len = run_source(&mut vm, "var f=new Function('a','b','return a'); f.length");
assert_eq!(len, JsValue::int(2));
}
#[test]
fn dynamic_function_comma_split_params_count() {
let mut vm = Vm::new();
// 单个实参 "a,b,c" 拼接解析为 3 个形参,length 应为解析后的形参数。
let result = run_source(&mut vm, "new Function('a,b,c','null').length");
assert_eq!(result, JsValue::int(3));
}
#[test]
fn dynamic_function_name_and_length_attributes() {
let mut vm = Vm::new();
// length/name 为不可写、不可枚举、可配置的数据属性。
let attrs = run_source(
&mut vm,
"var d=Object.getOwnPropertyDescriptor(new Function('a','return a'),'length'); String(d.value)+d.writable+d.enumerable+d.configurable",
);
assert_eq!(vm.lookup_str(attrs).as_deref(), Some("1falsefalsetrue"));
let name_attrs = run_source(
&mut vm,
"var d=Object.getOwnPropertyDescriptor(Function(),'name'); String(d.writable)+d.enumerable+d.configurable",
);
assert_eq!(vm.lookup_str(name_attrs).as_deref(), Some("falsefalsetrue"));
}
#[test]
fn dynamic_function_rethrows_to_string_exception() {
let mut vm = Vm::new();
// 形参 ToString 回调抛出的原始值须原样传播,而非包成 TypeError。
let result = run_source(&mut vm, "try{new Function({toString:function(){throw 7}})}catch(e){e}");
assert_eq!(result, JsValue::int(7));
}
#[test]
fn session_epoch_reset_is_only_in_full_reset_state_clear() {
let src = include_str!("vm_support.rs");
let production = src.split("#[cfg(test)]").next().expect("production source");
assert_eq!(production.matches("self.gc_state.session_epoch.reset()").count(), 1);
assert!(production.contains("fn clear_full_reset_state(&mut self)"));
assert!(production.contains("self.gc_state.session_epoch.reset();"));
}
#[test]
fn full_reset_refreshes_object_prototype_after_object_dirty() {
let mut vm = Vm::new();
let old_object_proto = vm.session.builtin_world().object_proto.as_ptr();
unsafe { &mut *(old_object_proto as *mut JsObject) }.bump_generation();
vm.full_reset();
assert!(!std::ptr::eq(old_object_proto, vm.session.builtin_world().object_proto.as_ptr()));
assert!(std::ptr::eq(
vm.object_prototype.as_ptr(),
vm.session.builtin_world().object_proto.as_ptr()
));
assert!(!vm.session.is_dirty_since_snapshot());
}
#[test]
fn full_reset_object_dirty_rebinds_iterator_family() {
let mut vm = Vm::new();
let old_object_proto = vm.session.builtin_world().object_proto.as_ptr();
let old_iterator_proto = vm.session.builtin_world().iterator_proto.as_ptr();
// 用户修改 Object.prototype:object 家族世代递增,global 未动。
unsafe { &mut *(old_object_proto as *mut JsObject) }.bump_generation();
vm.full_reset();
// object 家族与迭代器原型全部重建(新原型链到新 Object.prototype)。
assert!(!std::ptr::eq(old_object_proto, vm.session.builtin_world().object_proto.as_ptr()));
assert!(!std::ptr::eq(old_iterator_proto, vm.session.builtin_world().iterator_proto.as_ptr()));
// global 保留(dirty.global=false):其 Iterator 函数对象的 prototype
// 属性须对齐到重建后的 %IteratorPrototype%。
let iter_val = global_prop(&vm, "Iterator");
let si_prototype = vm.kernel_core.perm_interner().intern("prototype").0;
let iter_obj = unsafe { &*iter_val.as_js_object_ptr() };
let proto_pos = vm
.kernel_core
.shape_forge()
.lookup_position(iter_obj.shape_id(), si_prototype)
.expect("Iterator should have prototype slot");
assert!(std::ptr::eq(
iter_obj.get_prop_at(proto_pos).as_js_object_ptr(),
vm.session.builtin_world().iterator_proto.as_ptr() as *mut JsObject
));
// full_reset 后 session 干净;此后 run_source 执行才重新累积世代变化。
assert!(!vm.session.is_dirty_since_snapshot());
// 迭代器家族功能完整:原型 next 就位,for-of/spread/Array.from/Iterator.from/
// Map/Set/String/yield* 全部可用,原型链与 Iterator.prototype 一致。
let r = run_source(&mut vm, "[...[1,2,3]].join(',')");
assert_eq!(vm.lookup_str(r).as_deref(), Some("1,2,3"));
let r = run_source(&mut vm, "Array.from([1,2]).join(',')");
assert_eq!(vm.lookup_str(r).as_deref(), Some("1,2"));
let r = run_source(&mut vm, "Iterator.from({next:function(){return {value:42,done:false}}}).next().value");
assert_eq!(r, JsValue::int(42));
let r = run_source(&mut vm, "[...new Map([[1,2],[3,4]])].map(function(x){return x.join(':')}).join(';')");
assert_eq!(vm.lookup_str(r).as_deref(), Some("1:2;3:4"));
let r = run_source(&mut vm, "[...new Set([1,2,3])].join(',')");
assert_eq!(vm.lookup_str(r).as_deref(), Some("1,2,3"));
let r = run_source(&mut vm, "[...'ab'].join(',')");
assert_eq!(vm.lookup_str(r).as_deref(), Some("a,b"));
let r = run_source(&mut vm, "function* g(){yield* [1,2]} [...g()].join(',')");
assert_eq!(vm.lookup_str(r).as_deref(), Some("1,2"));
let r = run_source(
&mut vm,
"Object.getPrototypeOf(Object.getPrototypeOf([].values())) === Iterator.prototype",
);
assert_eq!(r, JsValue::bool(true));
let r = run_source(&mut vm, "var it=[].values(); it[Symbol.iterator]()===it");
assert_eq!(r, JsValue::bool(true));
}
#[test]
fn full_reset_global_dirty_keeps_iterator_proto_slots_stable() {
let mut vm = Vm::new();
let arr_iter_proto = vm.session.builtin_world().array_iterator_proto.as_ptr() as *mut JsObject;
let slots_before = unsafe { &*arr_iter_proto }.hash_props_vec().map_or(0, |v| v.len());
// global 世代递增(用户写 global),builtin 家族未动。
unsafe { &mut *(vm.session.global_object.as_ptr() as *mut JsObject) }.bump_generation();
vm.full_reset();
// builtin 未脏 → 迭代器原型保留原对象且属性槽不膨胀(重复 full_reset 不再追加)。
let arr_iter_proto_after = vm.session.builtin_world().array_iterator_proto.as_ptr() as *mut JsObject;
assert!(std::ptr::eq(arr_iter_proto, arr_iter_proto_after));
let slots_after = unsafe { &*arr_iter_proto_after }.hash_props_vec().map_or(0, |v| v.len());
assert_eq!(slots_before, slots_after);
assert!(!vm.session.is_dirty_since_snapshot());
// 迭代器功能经保留原型仍完整(run_source 起再次累积世代变化)。
let r = run_source(&mut vm, "[...[1,2,3]].join(',')");
assert_eq!(vm.lookup_str(r).as_deref(), Some("1,2,3"));
let r = run_source(&mut vm, "var it=new Set([1]).values(); it[Symbol.iterator]()===it");
assert_eq!(r, JsValue::bool(true));
}
#[test]
fn bigint_literal_arithmetic_and_comparison() {
let mut vm = Vm::new();
assert_eq!(run_source(&mut vm, "100n + 23n"), run_source(&mut vm, "123n"));
assert_eq!(run_source(&mut vm, "100n - 30n"), run_source(&mut vm, "70n"));
assert_eq!(run_source(&mut vm, "7n * 6n"), run_source(&mut vm, "42n"));
assert_eq!(run_source(&mut vm, "10n / 4n"), run_source(&mut vm, "2n"));
assert_eq!(run_source(&mut vm, "10n % 3n"), run_source(&mut vm, "1n"));
assert_eq!(run_source(&mut vm, "-7n"), run_source(&mut vm, "0n - 7n"));
assert_eq!(run_source(&mut vm, "123n == 123n"), JsValue::bool(true));
assert_eq!(run_source(&mut vm, "123n === 123n"), JsValue::bool(true));
assert_eq!(run_source(&mut vm, "5n < 3n"), JsValue::bool(false));
assert_eq!(run_source(&mut vm, "5n > 3n"), JsValue::bool(true));
assert_eq!(run_source(&mut vm, "1n === 1"), JsValue::bool(false));
let typeof_result = run_source(&mut vm, "typeof 123n");
assert!(typeof_result.is_string());
assert_eq!(vm.lookup_str(typeof_result).as_deref(), Some("bigint"));
}
#[test]
fn bigint_constructor_and_string() {
let mut vm = Vm::new();
assert_eq!(run_source(&mut vm, "BigInt(42)"), run_source(&mut vm, "42n"));
assert_eq!(run_source(&mut vm, "BigInt('123')"), run_source(&mut vm, "123n"));
assert_eq!(run_source(&mut vm, "BigInt('0x10')"), run_source(&mut vm, "16n"));
let s = run_source(&mut vm, "String(123n)");
assert!(s.is_string());
assert_eq!(vm.lookup_str(s).as_deref(), Some("123"));
let ts = run_source(&mut vm, "(123n).toString()");
assert!(ts.is_string());
assert_eq!(vm.lookup_str(ts).as_deref(), Some("123"));
assert_eq!(run_source(&mut vm, "Number(5n)"), JsValue::int(5));
}
#[test]
fn bigint_mixed_type_throws() {
let mut vm = Vm::new();
let te = run_source(&mut vm, "try { 1n + 1 } catch(e) { e.name }");
assert_eq!(vm.lookup_str(te).as_deref(), Some("TypeError"));
let re = run_source(&mut vm, "try { 1n / 0n } catch(e) { e.name }");
assert_eq!(vm.lookup_str(re).as_deref(), Some("RangeError"));
let ne = run_source(&mut vm, "try { new BigInt(1) } catch(e) { e.name }");
assert_eq!(vm.lookup_str(ne).as_deref(), Some("TypeError"));
}
#[test]
fn bigint_survives_reset_and_gc() {
let mut vm = Vm::new();
let result = run_source(&mut vm, "100n + 23n");
assert!(result.is_bigint());
assert_eq!(vm.bigint_value(result), &num_bigint::BigInt::from(123));
vm.reset();
// reset 保留 session 字符串/bigint box:值仍可读。
assert_eq!(vm.bigint_value(result), &num_bigint::BigInt::from(123));
}
#[test]
fn bigint_wrapped_and_number_comparison() {
let mut vm = Vm::new();
// 包装对象 coerce 后双 BigInt 运算。
assert_eq!(run_source(&mut vm, "Object(2n) / 2n"), run_source(&mut vm, "1n"));
assert_eq!(run_source(&mut vm, "Object(2n) * 3n"), run_source(&mut vm, "6n"));
assert_eq!(run_source(&mut vm, "2n + Object(3n)"), run_source(&mut vm, "5n"));
// BigInt 与 Number 精确关系比较(超出 f64 精度仍精确)。
assert_eq!(run_source(&mut vm, "9007199254740993n > 9007199254740992"), JsValue::bool(true));
assert_eq!(run_source(&mut vm, "9007199254740993n < 9007199254740994"), JsValue::bool(true));
assert_eq!(run_source(&mut vm, "2n < 3"), JsValue::bool(true));
assert_eq!(run_source(&mut vm, "3n >= 3"), JsValue::bool(true));
// NaN 关系比较为 false。
assert_eq!(run_source(&mut vm, "0n < NaN"), JsValue::bool(false));
// 混合算术抛 TypeError。
let te = run_source(&mut vm, "try { Object(1n) - 1 } catch(e) { e.name }");
assert_eq!(vm.lookup_str(te).as_deref(), Some("TypeError"));
}
}