mirror of
https://github.com/pjh456/OxideJS.git
synced 2026-10-26 19:51:11 +08:00
refactor: vm.rs 内联测试按属主模块归位
This commit is contained in:
@@ -387,4 +387,36 @@ mod tests {
|
|||||||
assert_eq!(bytecode[1], 0x0000_0040, "BREAK ext 字保持原值");
|
assert_eq!(bytecode[1], 0x0000_0040, "BREAK ext 字保持原值");
|
||||||
assert_eq!(&bytecode[3..=10], &[0; 8], "后续 IC 扩展字被清零");
|
assert_eq!(&bytecode[3..=10], &[0; 8], "后续 IC 扩展字被清零");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn write_ic_back_updates_slot_zero_ext_words() {
|
||||||
|
let mut bc = vec![0u32; IC_EXT_WORDS];
|
||||||
|
write_ic_back(bc.as_mut_slice(), IC_EXT_WORDS, 0x1234_5678, 7, 0);
|
||||||
|
assert_eq!(bc[0], 0x0034_5678);
|
||||||
|
assert_eq!(bc[1], 7);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn write_ic_back_rolls_fifo_and_drops_oldest_slot() {
|
||||||
|
// 预置 8 字四槽(2 字/槽):槽 0=(0xA1,1,0)、槽 1=(0xA2,2,1)、槽 2=(0xA3,3,0)、槽 3=(0xA4,4,0)。
|
||||||
|
let mut bc = vec![0u32; IC_EXT_WORDS];
|
||||||
|
bc[0] = 0xA1;
|
||||||
|
bc[1] = 1;
|
||||||
|
bc[2] = 0xA2 | (1 << 24);
|
||||||
|
bc[3] = 2;
|
||||||
|
bc[4] = 0xA3;
|
||||||
|
bc[5] = 3;
|
||||||
|
bc[6] = 0xA4;
|
||||||
|
bc[7] = 4;
|
||||||
|
write_ic_back(bc.as_mut_slice(), IC_EXT_WORDS, 0xB0, 9, 2);
|
||||||
|
// 新条目进槽 0;原槽 0..2 顺移到槽 1..3;最老槽 3 丢弃。
|
||||||
|
assert_eq!(bc[0], 0xB0 | (2 << 24));
|
||||||
|
assert_eq!(bc[1], 9);
|
||||||
|
assert_eq!(bc[2], 0xA1);
|
||||||
|
assert_eq!(bc[3], 1);
|
||||||
|
assert_eq!(bc[4], 0xA2 | (1 << 24));
|
||||||
|
assert_eq!(bc[5], 2);
|
||||||
|
assert_eq!(bc[6], 0xA3);
|
||||||
|
assert_eq!(bc[7], 3);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -278,3 +278,216 @@ impl Vm {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
use oxide_bytecode::module::CompiledModule;
|
||||||
|
use oxide_bytecode::opcode;
|
||||||
|
use oxide_runtime_api::VmHost;
|
||||||
|
use oxide_types::object::NativeFnPtr;
|
||||||
|
|
||||||
|
fn native_return_last_arg(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
||||||
|
let reg = *args.last().expect("receiver + args");
|
||||||
|
NativeResult::Ok(vm.reg(reg))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn native_return_arg_count(_vm: &mut Vm, args: &[u8]) -> NativeResult {
|
||||||
|
NativeResult::Ok(JsValue::int(args.len().saturating_sub(1) as i32))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn native_return_full_arg_count(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
||||||
|
NativeResult::Ok(JsValue::int(vm.native_arg_count(args) as i32))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn native_return_last_full_arg(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
||||||
|
let n = vm.native_arg_count(args);
|
||||||
|
NativeResult::Ok(vm.native_arg_at(args, n - 1))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn native_nested_inline_254(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
||||||
|
// 外层 native 回调:receiver 落 regs[253](native 分支单存槽),内嵌执行
|
||||||
|
// n_registers=254 的 inline 字节码回调后,receiver 槽必须保持外层值。
|
||||||
|
let receiver = vm.reg(args[0]);
|
||||||
|
let callee = vm.reg(args[1]);
|
||||||
|
let callee_obj = unsafe { &*callee.as_js_object_ptr() };
|
||||||
|
let mut call_args = Vec::with_capacity(args.len().saturating_sub(2));
|
||||||
|
for &r in &args[2..] {
|
||||||
|
call_args.push(vm.reg(r));
|
||||||
|
}
|
||||||
|
let kept = match vm.call_bytecode_function_inline(callee, callee_obj, receiver, &call_args) {
|
||||||
|
Ok(_) => vm.regs[253] == receiver,
|
||||||
|
Err(_) => false,
|
||||||
|
};
|
||||||
|
NativeResult::Ok(JsValue::int(if kept { 1 } else { 0 }))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn native_function(vm: &mut Vm, f: crate::native::NativeFn) -> JsValue {
|
||||||
|
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 是 NativeFn 函数项,可作为 NativeFnPtr 存储。
|
||||||
|
obj.set_native_fn(Some(unsafe { NativeFnPtr::from_raw(f as *const ()) }));
|
||||||
|
JsValue::object(vm.alloc_object(obj) as *mut u8)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn call_function_sync_passes_high_arity_native_args_without_truncation() {
|
||||||
|
let mut vm = Vm::new();
|
||||||
|
let callee = native_function(&mut vm, native_return_last_arg);
|
||||||
|
let args: Vec<JsValue> = (0..20).map(JsValue::int).collect();
|
||||||
|
|
||||||
|
let result = vm
|
||||||
|
.call_function_sync(callee, JsValue::undefined(), &args)
|
||||||
|
.expect("high-arity sync call should succeed");
|
||||||
|
|
||||||
|
assert_eq!(result, JsValue::int(19));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn call_function_sync_reports_actual_native_arg_count() {
|
||||||
|
let mut vm = Vm::new();
|
||||||
|
let callee = native_function(&mut vm, native_return_arg_count);
|
||||||
|
let args: Vec<JsValue> = (0..32).map(JsValue::int).collect();
|
||||||
|
|
||||||
|
let result = vm
|
||||||
|
.call_function_sync(callee, JsValue::undefined(), &args)
|
||||||
|
.expect("arg count should be preserved");
|
||||||
|
|
||||||
|
assert_eq!(result, JsValue::int(32));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn call_function_sync_overflow_preserves_large_native_arity_and_registers() {
|
||||||
|
// 大实参集(超寄存器窗口 253)经 spill 溢出区完整送达 native:
|
||||||
|
// 全量计数与末位实参都可读,且调用方寄存器不被打包过程污染。
|
||||||
|
let mut vm = Vm::new();
|
||||||
|
let callee = native_function(&mut vm, native_return_full_arg_count);
|
||||||
|
vm.set_reg(1, JsValue::int(7));
|
||||||
|
vm.set_reg(253, JsValue::int(11));
|
||||||
|
vm.set_reg(254, JsValue::int(12));
|
||||||
|
vm.set_reg(255, JsValue::int(13));
|
||||||
|
|
||||||
|
let args: Vec<JsValue> = (0..(Vm::SYNC_NATIVE_ARG_LIMIT + 100)).map(|i| JsValue::int(i as i32)).collect();
|
||||||
|
let count = vm
|
||||||
|
.call_function_sync(callee, JsValue::undefined(), &args)
|
||||||
|
.expect("大实参集应经 spill 溢出区完整送达 native");
|
||||||
|
assert_eq!(count, JsValue::int(args.len() as i32));
|
||||||
|
|
||||||
|
let last_callee = native_function(&mut vm, native_return_last_full_arg);
|
||||||
|
let last = vm
|
||||||
|
.call_function_sync(last_callee, JsValue::undefined(), &args)
|
||||||
|
.expect("溢出区末位实参应可读");
|
||||||
|
assert_eq!(last, JsValue::int((args.len() - 1) as i32));
|
||||||
|
|
||||||
|
assert_eq!(vm.reg(1), JsValue::int(7));
|
||||||
|
assert_eq!(vm.reg(253), JsValue::int(11));
|
||||||
|
assert_eq!(vm.reg(254), JsValue::int(12));
|
||||||
|
assert_eq!(vm.reg(255), JsValue::int(13));
|
||||||
|
// 溢出区随调用结束截断回收,不残留 spill 增长。
|
||||||
|
assert!(vm.spill_stack.is_empty(), "溢出区应已回收: {:?}", vm.spill_stack);
|
||||||
|
assert_eq!(vm.native_overflow_count, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 构造 `n_registers = 254` 的子模块:RegAlloc 合法产物(builtin 槽落 253 或
|
||||||
|
/// 高活度着色),其函数体写物理槽 253 后返回。
|
||||||
|
fn sub_module_254_with_r253_write() -> CompiledModule {
|
||||||
|
CompiledModule {
|
||||||
|
bytecode: Arc::from(vec![
|
||||||
|
opcode::encode(opcode::OpCode::MOV, 253, 0, 0),
|
||||||
|
opcode::encode(opcode::OpCode::HALT, 0, 0, 0),
|
||||||
|
]),
|
||||||
|
n_registers: 254,
|
||||||
|
..CompiledModule::new()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn inline_callee_254_registers_preserves_caller_active_r253() {
|
||||||
|
// 窗口化边界回归:inline 回调 callee 的 n_registers = 254(写物理槽 253)
|
||||||
|
// 且调用方 active_reg_limit = 254(regs[253] 为活动值)时,调用后
|
||||||
|
// regs[253] 必须恢复为调用方值,不得被 callee 写值覆盖。
|
||||||
|
let mut vm = Vm::new();
|
||||||
|
vm.install_module_table_for_test(Arc::new(vec![
|
||||||
|
Arc::new(CompiledModule::new()),
|
||||||
|
Arc::new(sub_module_254_with_r253_write()),
|
||||||
|
]));
|
||||||
|
vm.active_reg_limit = 254;
|
||||||
|
vm.regs[253] = JsValue::int(42);
|
||||||
|
|
||||||
|
let callee = vm.create_function_object(1, vm.current_gen, false, false, false, false);
|
||||||
|
let callee_obj = unsafe { &*callee.as_js_object_ptr() };
|
||||||
|
let result = vm
|
||||||
|
.call_bytecode_function_inline(callee, callee_obj, JsValue::undefined(), &[])
|
||||||
|
.expect("inline call should succeed");
|
||||||
|
|
||||||
|
assert_eq!(result, JsValue::undefined());
|
||||||
|
assert_eq!(vm.regs[253], JsValue::int(42), "调用方 regs[253] 活动值不得被 callee 覆盖");
|
||||||
|
assert_eq!(vm.active_reg_limit, 254, "restore 应还原调用方活动寄存器上限");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn native_callback_nested_254_register_inline_callee_keeps_receiver() {
|
||||||
|
// 嵌套回归防线:native 回调体(receiver 落 regs[253])内嵌 n_registers=254
|
||||||
|
// 的 inline 字节码回调时,内层写 regs[253] 不得污染外层 receiver 槽。
|
||||||
|
let mut vm = Vm::new();
|
||||||
|
vm.install_module_table_for_test(Arc::new(vec![
|
||||||
|
Arc::new(CompiledModule::new()),
|
||||||
|
Arc::new(sub_module_254_with_r253_write()),
|
||||||
|
]));
|
||||||
|
vm.active_reg_limit = 254;
|
||||||
|
vm.regs[253] = JsValue::int(7);
|
||||||
|
vm.regs[254] = JsValue::int(8);
|
||||||
|
|
||||||
|
let inner_callee = vm.create_function_object(1, vm.current_gen, false, false, false, false);
|
||||||
|
let outer_native = native_function(&mut vm, native_nested_inline_254);
|
||||||
|
let result = vm
|
||||||
|
.call_function_sync(outer_native, JsValue::int(99), &[inner_callee])
|
||||||
|
.expect("native callback should succeed");
|
||||||
|
|
||||||
|
assert_eq!(result, JsValue::int(1), "内嵌 inline 回调后外层 receiver 槽必须保持原值");
|
||||||
|
assert_eq!(vm.regs[253], JsValue::int(7), "native 分支恢复后调用方 regs[253] 保持");
|
||||||
|
assert_eq!(vm.regs[254], JsValue::int(8), "native 分支恢复后调用方 regs[254] 保持");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn push_bytecode_frame_param_overlap_reads_spill_first() {
|
||||||
|
// 实参源区间 regs[1..3) 与 callee 形参写入区 regs[2..4) 重叠(first < param_base):
|
||||||
|
// 压帧必须先拷 spill 实参区、形参再从 spill 区取源,避免边写形参边读实参
|
||||||
|
// 造成先写后读串值(形参 b 与 spill 实参区都取错)。
|
||||||
|
let mut vm = Vm::new();
|
||||||
|
// sub_modules[1] = callee:2 个形参,param_base=2(与调用方实参槽 2 重叠)
|
||||||
|
let mut callee_mod = CompiledModule::new();
|
||||||
|
callee_mod.n_args = 2;
|
||||||
|
callee_mod.param_base = 2;
|
||||||
|
callee_mod.n_registers = 5;
|
||||||
|
callee_mod.bytecode = Arc::from(vec![opcode::encode(opcode::OpCode::RETURN, 0, 0, 0)]);
|
||||||
|
vm.install_module_table_for_test(Arc::new(vec![Arc::new(CompiledModule::new()), Arc::new(callee_mod)]));
|
||||||
|
vm.active_reg_limit = 8;
|
||||||
|
// 调用方实参区 regs[1..3):arg0=10, arg1=20;regs[2] 同时是 callee 形参槽(param_base=2)
|
||||||
|
vm.regs[1] = JsValue::int(10);
|
||||||
|
vm.regs[2] = JsValue::int(20);
|
||||||
|
|
||||||
|
let callee = vm.create_function_object(1, vm.current_gen, false, false, false, false);
|
||||||
|
vm.push_bytecode_frame(
|
||||||
|
callee,
|
||||||
|
JsValue::undefined(),
|
||||||
|
super::FrameArgs::RegRange { first: 1, count: 2 },
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
JsValue::undefined(),
|
||||||
|
super::FrameContinuation::None,
|
||||||
|
0,
|
||||||
|
)
|
||||||
|
.expect("压帧成功");
|
||||||
|
// 形参从 spill 实参区取源:regs[2]=arg0=10,regs[3]=arg1=20(不得被先写覆盖)
|
||||||
|
assert_eq!(vm.regs[2], JsValue::int(10), "形参 a 应为实参 arg0");
|
||||||
|
assert_eq!(vm.regs[3], JsValue::int(20), "形参 b 应为实参 arg1(不受先写覆盖)");
|
||||||
|
// spill 实参区(arguments 对象源)保持原实参值
|
||||||
|
let frame = vm.frames.last().expect("压帧后应有帧");
|
||||||
|
let base = frame.arguments_base as usize;
|
||||||
|
assert_eq!(vm.spill_stack[base], JsValue::int(10), "spill 实参区 arg0");
|
||||||
|
assert_eq!(vm.spill_stack[base + 1], JsValue::int(20), "spill 实参区 arg1");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -14,8 +14,6 @@ use smallvec::SmallVec;
|
|||||||
pub use crate::bindings::init_kernel_builtins;
|
pub use crate::bindings::init_kernel_builtins;
|
||||||
use crate::native::NativeFn;
|
use crate::native::NativeFn;
|
||||||
use crate::vm_debug;
|
use crate::vm_debug;
|
||||||
#[cfg(test)]
|
|
||||||
use crate::vm_state::ForOfEntry;
|
|
||||||
use crate::vm_state::{GcState, IterState, ProfilingState, SymbolState};
|
use crate::vm_state::{GcState, IterState, ProfilingState, SymbolState};
|
||||||
use oxide_kernel::kernel::{KernelCore, KernelSession};
|
use oxide_kernel::kernel::{KernelCore, KernelSession};
|
||||||
use oxide_types::error::JsErrorKind;
|
use oxide_types::error::JsErrorKind;
|
||||||
@@ -428,564 +426,7 @@ impl Vm {
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::{canonical_index_of, canonical_index_units};
|
use super::{canonical_index_of, canonical_index_units};
|
||||||
use super::{opcode, ForOfEntry, JsValue, TryHandler, Vm};
|
|
||||||
use oxide_bytecode::module::CompiledModule;
|
|
||||||
use oxide_runtime_api::{NativeResult, VmHost};
|
|
||||||
use oxide_types::object::NativeFnPtr;
|
|
||||||
use oxide_types::object::{JsObject, PropAttributes};
|
|
||||||
use oxide_types::private_key::INT_KEY_COUNT;
|
use oxide_types::private_key::INT_KEY_COUNT;
|
||||||
use std::sync::Arc;
|
|
||||||
|
|
||||||
fn native_return_7(_vm: &mut Vm, _args: &[u8]) -> NativeResult {
|
|
||||||
NativeResult::Ok(JsValue::int(7))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn native_get_marker(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
|
||||||
let this_val = vm.reg(args[0]);
|
|
||||||
if !this_val.is_object() {
|
|
||||||
return NativeResult::Ok(JsValue::undefined());
|
|
||||||
}
|
|
||||||
let marker_si = vm.kernel_core.perm_interner().intern("marker").0;
|
|
||||||
let obj = unsafe { &*this_val.as_js_object_ptr() };
|
|
||||||
NativeResult::Ok(vm.resolve_property(obj, marker_si).unwrap_or(JsValue::undefined()))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn native_set_marker(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
|
||||||
let this_val = vm.reg(args[0]);
|
|
||||||
let value = vm.reg(args[1]);
|
|
||||||
if !this_val.is_object() {
|
|
||||||
return NativeResult::Ok(JsValue::undefined());
|
|
||||||
}
|
|
||||||
let marker_si = vm.kernel_core.perm_interner().intern("marker").0;
|
|
||||||
let obj = unsafe { &mut *this_val.as_js_object_ptr() };
|
|
||||||
vm.set_or_create_prop_value(obj, marker_si, value);
|
|
||||||
NativeResult::Ok(JsValue::undefined())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn native_return_last_arg(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
|
||||||
let reg = *args.last().expect("receiver + args");
|
|
||||||
NativeResult::Ok(vm.reg(reg))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn native_return_arg_count(_vm: &mut Vm, args: &[u8]) -> NativeResult {
|
|
||||||
NativeResult::Ok(JsValue::int(args.len().saturating_sub(1) as i32))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn native_return_full_arg_count(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
|
||||||
NativeResult::Ok(JsValue::int(vm.native_arg_count(args) as i32))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn native_return_last_full_arg(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
|
||||||
let n = vm.native_arg_count(args);
|
|
||||||
NativeResult::Ok(vm.native_arg_at(args, n - 1))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn native_nested_inline_254(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
|
||||||
// 外层 native 回调:receiver 落 regs[253](native 分支单存槽),内嵌执行
|
|
||||||
// n_registers=254 的 inline 字节码回调后,receiver 槽必须保持外层值。
|
|
||||||
let receiver = vm.reg(args[0]);
|
|
||||||
let callee = vm.reg(args[1]);
|
|
||||||
let callee_obj = unsafe { &*callee.as_js_object_ptr() };
|
|
||||||
let mut call_args = Vec::with_capacity(args.len().saturating_sub(2));
|
|
||||||
for &r in &args[2..] {
|
|
||||||
call_args.push(vm.reg(r));
|
|
||||||
}
|
|
||||||
let kept = match vm.call_bytecode_function_inline(callee, callee_obj, receiver, &call_args) {
|
|
||||||
Ok(_) => vm.regs[253] == receiver,
|
|
||||||
Err(_) => false,
|
|
||||||
};
|
|
||||||
NativeResult::Ok(JsValue::int(if kept { 1 } else { 0 }))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn native_function(vm: &mut Vm, f: crate::native::NativeFn) -> JsValue {
|
|
||||||
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 是 NativeFn 函数项,可作为 NativeFnPtr 存储。
|
|
||||||
obj.set_native_fn(Some(unsafe { NativeFnPtr::from_raw(f as *const ()) }));
|
|
||||||
JsValue::object(vm.alloc_object(obj) as *mut u8)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn plain_object(vm: &mut Vm) -> JsValue {
|
|
||||||
let proto = vm.session.builtin_world().object_proto.as_ptr() as *mut JsObject;
|
|
||||||
let obj = JsObject::new_empty(oxide_kernel::shape_forge::EMPTY_SHAPE_ID, JsValue::from_js_object(proto));
|
|
||||||
JsValue::object(vm.alloc_object(obj) as *mut u8)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn add_accessor(vm: &mut Vm, obj_val: JsValue, name: &str, get: JsValue, set: JsValue) {
|
|
||||||
let si = vm.kernel_core.perm_interner().intern(name).0;
|
|
||||||
let obj = unsafe { &mut *obj_val.as_js_object_ptr() };
|
|
||||||
let shape_id = vm.kernel_core.shape_forge().make_shape(obj.shape_id(), si);
|
|
||||||
obj.set_shape_id(shape_id);
|
|
||||||
let pos = obj.push_prop(JsValue::undefined());
|
|
||||||
obj.set_accessor_meta(pos, get, set, PropAttributes::DEFAULT_DATA);
|
|
||||||
obj.bump_generation();
|
|
||||||
}
|
|
||||||
|
|
||||||
fn set_data(vm: &mut Vm, obj_val: JsValue, name: &str, val: JsValue) {
|
|
||||||
let si = vm.kernel_core.perm_interner().intern(name).0;
|
|
||||||
let obj = unsafe { &mut *obj_val.as_js_object_ptr() };
|
|
||||||
vm.set_or_create_prop_value(obj, si, val);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn reset_clears_runtime_state_like_rerun() {
|
|
||||||
let mut vm = Vm::new();
|
|
||||||
vm.regs[1] = JsValue::int(7);
|
|
||||||
vm.pc = 3;
|
|
||||||
vm.frames.push(super::CallFrame {
|
|
||||||
return_addr: 1,
|
|
||||||
function_name: 0,
|
|
||||||
caller_reg_limit: 2,
|
|
||||||
caller_active_reg_limit: 2,
|
|
||||||
saved_reg_offset: 0,
|
|
||||||
spill_offset: 0,
|
|
||||||
arguments_base: 0,
|
|
||||||
arguments_count: 0,
|
|
||||||
saved_this: JsValue::undefined(),
|
|
||||||
saved_new_target: JsValue::undefined(),
|
|
||||||
callee: JsValue::undefined(),
|
|
||||||
construct_result_reg: None,
|
|
||||||
strict: false,
|
|
||||||
constructed_this: None,
|
|
||||||
is_derived_constructor: false,
|
|
||||||
super_called: false,
|
|
||||||
continuation: super::FrameContinuation::None,
|
|
||||||
});
|
|
||||||
vm.save_stack.push(JsValue::undefined());
|
|
||||||
vm.iters
|
|
||||||
.for_in_iters
|
|
||||||
.push(std::ptr::dangling_mut::<super::ForInIter<'static>>());
|
|
||||||
vm.iters.for_of_iters.push(ForOfEntry {
|
|
||||||
iterator: JsValue::undefined(),
|
|
||||||
last_result: JsValue::undefined(),
|
|
||||||
is_async: false,
|
|
||||||
});
|
|
||||||
vm.saved_bytecode_stack
|
|
||||||
.push(Arc::from(vec![opcode::encode(opcode::OpCode::HALT, 0, 0, 0)]));
|
|
||||||
vm.saved_immutables_stack
|
|
||||||
.push(std::ptr::slice_from_raw_parts(std::ptr::null(), 0));
|
|
||||||
vm.try_stack.push(TryHandler {
|
|
||||||
catch_pc: Some(1),
|
|
||||||
finally_pc: None,
|
|
||||||
finally_active: false,
|
|
||||||
frame_depth: 0,
|
|
||||||
for_of_depth: 0,
|
|
||||||
});
|
|
||||||
vm.exception_value = Some(JsValue::int(2));
|
|
||||||
vm.pending_exception = Some(JsValue::int(3));
|
|
||||||
vm.pending_error_kind = Some("TypeError");
|
|
||||||
|
|
||||||
vm.reset();
|
|
||||||
|
|
||||||
assert_eq!(vm.pc, 0);
|
|
||||||
assert!(vm.frames.is_empty());
|
|
||||||
assert!(vm.save_stack.is_empty());
|
|
||||||
assert!(vm.iters.for_in_iters.is_empty());
|
|
||||||
assert!(vm.iters.for_of_iters.is_empty());
|
|
||||||
assert!(vm.saved_bytecode_stack.is_empty());
|
|
||||||
assert!(vm.saved_immutables_stack.is_empty());
|
|
||||||
assert!(vm.try_stack.is_empty());
|
|
||||||
assert!(vm.exception_value.is_none());
|
|
||||||
assert!(vm.pending_exception.is_none());
|
|
||||||
assert!(vm.pending_error_kind.is_none());
|
|
||||||
assert!(vm.bytecode.is_empty());
|
|
||||||
assert!(vm.immutables().is_empty());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn full_reset_clears_symbol_state() {
|
|
||||||
let mut vm = Vm::new();
|
|
||||||
vm.symbols.intern(Some("shared".to_string()));
|
|
||||||
|
|
||||||
vm.full_reset();
|
|
||||||
|
|
||||||
assert_eq!(vm.symbols.symbol_counter, 0);
|
|
||||||
assert!(vm.symbols.symbol_descriptions.is_empty());
|
|
||||||
assert!(vm.symbols.symbol_registry.is_empty());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn for_of_close_pops_iterator_stack() {
|
|
||||||
let module = CompiledModule {
|
|
||||||
bytecode: Arc::from(vec![
|
|
||||||
opcode::encode(opcode::OpCode::FOR_OF_CLOSE, 0, 0, 0),
|
|
||||||
opcode::encode(opcode::OpCode::HALT, 0, 0, 0),
|
|
||||||
]),
|
|
||||||
n_registers: 1,
|
|
||||||
..CompiledModule::new()
|
|
||||||
};
|
|
||||||
let mut vm = Vm::new();
|
|
||||||
vm.iters.for_of_iters.push(ForOfEntry {
|
|
||||||
iterator: JsValue::undefined(),
|
|
||||||
last_result: JsValue::undefined(),
|
|
||||||
is_async: false,
|
|
||||||
});
|
|
||||||
|
|
||||||
vm.run(&Arc::new(module))
|
|
||||||
.expect("FOR_OF_CLOSE should tolerate non-object sentinel");
|
|
||||||
|
|
||||||
assert!(vm.iters.for_of_iters.is_empty());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn write_ic_back_updates_slot_zero_ext_words() {
|
|
||||||
let mut vm = Vm::new();
|
|
||||||
vm.bytecode = Arc::from(vec![0; oxide_bytecode::opcode::IC_EXT_WORDS]);
|
|
||||||
vm.pc = oxide_bytecode::opcode::IC_EXT_WORDS;
|
|
||||||
crate::ic_helper::write_ic_back(Arc::make_mut(&mut vm.bytecode), vm.pc, 0x1234_5678, 7, 0);
|
|
||||||
assert_eq!(vm.bytecode[0], 0x0034_5678);
|
|
||||||
assert_eq!(vm.bytecode[1], 7);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn write_ic_back_rolls_fifo_and_drops_oldest_slot() {
|
|
||||||
let mut vm = Vm::new();
|
|
||||||
// 预置 8 字四槽(2 字/槽):槽 0=(0xA1,1,0)、槽 1=(0xA2,2,1)、槽 2=(0xA3,3,0)、槽 3=(0xA4,4,0)。
|
|
||||||
let mut bc = vec![0u32; oxide_bytecode::opcode::IC_EXT_WORDS];
|
|
||||||
bc[0] = 0xA1;
|
|
||||||
bc[1] = 1;
|
|
||||||
bc[2] = 0xA2 | (1 << 24);
|
|
||||||
bc[3] = 2;
|
|
||||||
bc[4] = 0xA3;
|
|
||||||
bc[5] = 3;
|
|
||||||
bc[6] = 0xA4;
|
|
||||||
bc[7] = 4;
|
|
||||||
vm.bytecode = Arc::from(bc);
|
|
||||||
vm.pc = oxide_bytecode::opcode::IC_EXT_WORDS;
|
|
||||||
crate::ic_helper::write_ic_back(Arc::make_mut(&mut vm.bytecode), vm.pc, 0xB0, 9, 2);
|
|
||||||
// 新条目进槽 0;原槽 0..2 顺移到槽 1..3;最老槽 3 丢弃。
|
|
||||||
assert_eq!(vm.bytecode[0], 0xB0 | (2 << 24));
|
|
||||||
assert_eq!(vm.bytecode[1], 9);
|
|
||||||
assert_eq!(vm.bytecode[2], 0xA1);
|
|
||||||
assert_eq!(vm.bytecode[3], 1);
|
|
||||||
assert_eq!(vm.bytecode[4], 0xA2 | (1 << 24));
|
|
||||||
assert_eq!(vm.bytecode[5], 2);
|
|
||||||
assert_eq!(vm.bytecode[6], 0xA3);
|
|
||||||
assert_eq!(vm.bytecode[7], 3);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn unimplemented_profile_opcode_fails_explicitly() {
|
|
||||||
let module = CompiledModule {
|
|
||||||
bytecode: Arc::from(vec![
|
|
||||||
opcode::encode(opcode::OpCode::PROFILE_SHAPE, 0, 0, 0),
|
|
||||||
opcode::encode(opcode::OpCode::HALT, 0, 0, 0),
|
|
||||||
]),
|
|
||||||
n_registers: 1,
|
|
||||||
..CompiledModule::new()
|
|
||||||
};
|
|
||||||
let mut vm = Vm::new();
|
|
||||||
let err = vm
|
|
||||||
.run(&Arc::new(module))
|
|
||||||
.expect_err("unimplemented opcode should fail explicitly");
|
|
||||||
assert_eq!(err, "opcode PROFILE_SHAPE not yet implemented");
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn ordinary_get_calls_own_native_getter() {
|
|
||||||
let mut vm = Vm::new();
|
|
||||||
let obj_val = plain_object(&mut vm);
|
|
||||||
let getter = native_function(&mut vm, native_return_7);
|
|
||||||
add_accessor(&mut vm, obj_val, "x", getter, JsValue::undefined());
|
|
||||||
|
|
||||||
let x_si = vm.kernel_core.perm_interner().intern("x").0;
|
|
||||||
let obj = unsafe { &*obj_val.as_js_object_ptr() };
|
|
||||||
let value = vm.ordinary_get(obj, x_si, obj_val).expect("getter");
|
|
||||||
assert_eq!(value, JsValue::int(7));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn ordinary_set_calls_own_native_setter() {
|
|
||||||
let mut vm = Vm::new();
|
|
||||||
let obj_val = plain_object(&mut vm);
|
|
||||||
let setter = native_function(&mut vm, native_set_marker);
|
|
||||||
add_accessor(&mut vm, obj_val, "x", JsValue::undefined(), setter);
|
|
||||||
|
|
||||||
let x_si = vm.kernel_core.perm_interner().intern("x").0;
|
|
||||||
let obj = unsafe { &mut *obj_val.as_js_object_ptr() };
|
|
||||||
vm.ordinary_set(obj, x_si, JsValue::int(9), obj_val, true).expect("setter");
|
|
||||||
|
|
||||||
let marker_si = vm.kernel_core.perm_interner().intern("marker").0;
|
|
||||||
let obj = unsafe { &*obj_val.as_js_object_ptr() };
|
|
||||||
assert_eq!(vm.resolve_property(obj, marker_si), Some(JsValue::int(9)));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn inherited_getter_uses_original_receiver() {
|
|
||||||
let mut vm = Vm::new();
|
|
||||||
let proto_val = plain_object(&mut vm);
|
|
||||||
let child_val = plain_object(&mut vm);
|
|
||||||
let getter = native_function(&mut vm, native_get_marker);
|
|
||||||
add_accessor(&mut vm, proto_val, "x", getter, JsValue::undefined());
|
|
||||||
set_data(&mut vm, child_val, "marker", JsValue::int(42));
|
|
||||||
unsafe {
|
|
||||||
(*child_val.as_js_object_ptr()).set_proto(proto_val).expect("proto");
|
|
||||||
}
|
|
||||||
|
|
||||||
let x_si = vm.kernel_core.perm_interner().intern("x").0;
|
|
||||||
let child = unsafe { &*child_val.as_js_object_ptr() };
|
|
||||||
let value = vm.ordinary_get(child, x_si, child_val).expect("getter");
|
|
||||||
assert_eq!(value, JsValue::int(42));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn inherited_setter_uses_original_receiver() {
|
|
||||||
let mut vm = Vm::new();
|
|
||||||
let proto_val = plain_object(&mut vm);
|
|
||||||
let child_val = plain_object(&mut vm);
|
|
||||||
let setter = native_function(&mut vm, native_set_marker);
|
|
||||||
add_accessor(&mut vm, proto_val, "x", JsValue::undefined(), setter);
|
|
||||||
unsafe {
|
|
||||||
(*child_val.as_js_object_ptr()).set_proto(proto_val).expect("proto");
|
|
||||||
}
|
|
||||||
|
|
||||||
let x_si = vm.kernel_core.perm_interner().intern("x").0;
|
|
||||||
let child = unsafe { &mut *child_val.as_js_object_ptr() };
|
|
||||||
vm.ordinary_set(child, x_si, JsValue::int(12), child_val, true).expect("setter");
|
|
||||||
|
|
||||||
let marker_si = vm.kernel_core.perm_interner().intern("marker").0;
|
|
||||||
let child = unsafe { &*child_val.as_js_object_ptr() };
|
|
||||||
let proto = unsafe { &*proto_val.as_js_object_ptr() };
|
|
||||||
assert_eq!(vm.resolve_property(child, marker_si), Some(JsValue::int(12)));
|
|
||||||
assert_eq!(vm.resolve_property(proto, marker_si), None);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn deep_inherited_setter_uses_original_receiver() {
|
|
||||||
let mut vm = Vm::new();
|
|
||||||
let grand_proto_val = plain_object(&mut vm);
|
|
||||||
let proto_val = plain_object(&mut vm);
|
|
||||||
let child_val = plain_object(&mut vm);
|
|
||||||
let setter = native_function(&mut vm, native_set_marker);
|
|
||||||
add_accessor(&mut vm, grand_proto_val, "x", JsValue::undefined(), setter);
|
|
||||||
unsafe {
|
|
||||||
(*proto_val.as_js_object_ptr()).set_proto(grand_proto_val).expect("proto");
|
|
||||||
(*child_val.as_js_object_ptr()).set_proto(proto_val).expect("proto");
|
|
||||||
}
|
|
||||||
|
|
||||||
let x_si = vm.kernel_core.perm_interner().intern("x").0;
|
|
||||||
let child = unsafe { &mut *child_val.as_js_object_ptr() };
|
|
||||||
vm.ordinary_set(child, x_si, JsValue::int(15), child_val, true).expect("setter");
|
|
||||||
|
|
||||||
let marker_si = vm.kernel_core.perm_interner().intern("marker").0;
|
|
||||||
let child = unsafe { &*child_val.as_js_object_ptr() };
|
|
||||||
assert_eq!(vm.resolve_property(child, marker_si), Some(JsValue::int(15)));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn ordinary_data_property_still_reads_and_writes_without_meta() {
|
|
||||||
let mut vm = Vm::new();
|
|
||||||
let obj_val = plain_object(&mut vm);
|
|
||||||
set_data(&mut vm, obj_val, "x", JsValue::int(1));
|
|
||||||
|
|
||||||
let x_si = vm.kernel_core.perm_interner().intern("x").0;
|
|
||||||
let obj = unsafe { &mut *obj_val.as_js_object_ptr() };
|
|
||||||
assert!(!obj.has_prop_meta());
|
|
||||||
assert_eq!(vm.ordinary_get(obj, x_si, obj_val).expect("get"), JsValue::int(1));
|
|
||||||
vm.ordinary_set(obj, x_si, JsValue::int(2), obj_val, true).expect("set");
|
|
||||||
assert_eq!(vm.ordinary_get(obj, x_si, obj_val).expect("get"), JsValue::int(2));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn call_function_sync_passes_high_arity_native_args_without_truncation() {
|
|
||||||
let mut vm = Vm::new();
|
|
||||||
let callee = native_function(&mut vm, native_return_last_arg);
|
|
||||||
let args: Vec<JsValue> = (0..20).map(JsValue::int).collect();
|
|
||||||
|
|
||||||
let result = vm
|
|
||||||
.call_function_sync(callee, JsValue::undefined(), &args)
|
|
||||||
.expect("high-arity sync call should succeed");
|
|
||||||
|
|
||||||
assert_eq!(result, JsValue::int(19));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn call_function_sync_reports_actual_native_arg_count() {
|
|
||||||
let mut vm = Vm::new();
|
|
||||||
let callee = native_function(&mut vm, native_return_arg_count);
|
|
||||||
let args: Vec<JsValue> = (0..32).map(JsValue::int).collect();
|
|
||||||
|
|
||||||
let result = vm
|
|
||||||
.call_function_sync(callee, JsValue::undefined(), &args)
|
|
||||||
.expect("arg count should be preserved");
|
|
||||||
|
|
||||||
assert_eq!(result, JsValue::int(32));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn call_function_sync_overflow_preserves_large_native_arity_and_registers() {
|
|
||||||
// 大实参集(超寄存器窗口 253)经 spill 溢出区完整送达 native:
|
|
||||||
// 全量计数与末位实参都可读,且调用方寄存器不被打包过程污染。
|
|
||||||
let mut vm = Vm::new();
|
|
||||||
let callee = native_function(&mut vm, native_return_full_arg_count);
|
|
||||||
vm.set_reg(1, JsValue::int(7));
|
|
||||||
vm.set_reg(253, JsValue::int(11));
|
|
||||||
vm.set_reg(254, JsValue::int(12));
|
|
||||||
vm.set_reg(255, JsValue::int(13));
|
|
||||||
|
|
||||||
let args: Vec<JsValue> = (0..(Vm::SYNC_NATIVE_ARG_LIMIT + 100)).map(|i| JsValue::int(i as i32)).collect();
|
|
||||||
let count = vm
|
|
||||||
.call_function_sync(callee, JsValue::undefined(), &args)
|
|
||||||
.expect("大实参集应经 spill 溢出区完整送达 native");
|
|
||||||
assert_eq!(count, JsValue::int(args.len() as i32));
|
|
||||||
|
|
||||||
let last_callee = native_function(&mut vm, native_return_last_full_arg);
|
|
||||||
let last = vm
|
|
||||||
.call_function_sync(last_callee, JsValue::undefined(), &args)
|
|
||||||
.expect("溢出区末位实参应可读");
|
|
||||||
assert_eq!(last, JsValue::int((args.len() - 1) as i32));
|
|
||||||
|
|
||||||
assert_eq!(vm.reg(1), JsValue::int(7));
|
|
||||||
assert_eq!(vm.reg(253), JsValue::int(11));
|
|
||||||
assert_eq!(vm.reg(254), JsValue::int(12));
|
|
||||||
assert_eq!(vm.reg(255), JsValue::int(13));
|
|
||||||
// 溢出区随调用结束截断回收,不残留 spill 增长。
|
|
||||||
assert!(vm.spill_stack.is_empty(), "溢出区应已回收: {:?}", vm.spill_stack);
|
|
||||||
assert_eq!(vm.native_overflow_count, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 构造 `n_registers = 254` 的子模块:RegAlloc 合法产物(builtin 槽落 253 或
|
|
||||||
/// 高活度着色),其函数体写物理槽 253 后返回。
|
|
||||||
fn sub_module_254_with_r253_write() -> CompiledModule {
|
|
||||||
CompiledModule {
|
|
||||||
bytecode: Arc::from(vec![
|
|
||||||
opcode::encode(opcode::OpCode::MOV, 253, 0, 0),
|
|
||||||
opcode::encode(opcode::OpCode::HALT, 0, 0, 0),
|
|
||||||
]),
|
|
||||||
n_registers: 254,
|
|
||||||
..CompiledModule::new()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn inline_callee_254_registers_preserves_caller_active_r253() {
|
|
||||||
// 窗口化边界回归:inline 回调 callee 的 n_registers = 254(写物理槽 253)
|
|
||||||
// 且调用方 active_reg_limit = 254(regs[253] 为活动值)时,调用后
|
|
||||||
// regs[253] 必须恢复为调用方值,不得被 callee 写值覆盖。
|
|
||||||
let mut vm = Vm::new();
|
|
||||||
vm.install_module_table_for_test(Arc::new(vec![
|
|
||||||
Arc::new(CompiledModule::new()),
|
|
||||||
Arc::new(sub_module_254_with_r253_write()),
|
|
||||||
]));
|
|
||||||
vm.active_reg_limit = 254;
|
|
||||||
vm.regs[253] = JsValue::int(42);
|
|
||||||
|
|
||||||
let callee = vm.create_function_object(1, vm.current_gen, false, false, false, false);
|
|
||||||
let callee_obj = unsafe { &*callee.as_js_object_ptr() };
|
|
||||||
let result = vm
|
|
||||||
.call_bytecode_function_inline(callee, callee_obj, JsValue::undefined(), &[])
|
|
||||||
.expect("inline call should succeed");
|
|
||||||
|
|
||||||
assert_eq!(result, JsValue::undefined());
|
|
||||||
assert_eq!(vm.regs[253], JsValue::int(42), "调用方 regs[253] 活动值不得被 callee 覆盖");
|
|
||||||
assert_eq!(vm.active_reg_limit, 254, "restore 应还原调用方活动寄存器上限");
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn native_callback_nested_254_register_inline_callee_keeps_receiver() {
|
|
||||||
// 嵌套回归防线:native 回调体(receiver 落 regs[253])内嵌 n_registers=254
|
|
||||||
// 的 inline 字节码回调时,内层写 regs[253] 不得污染外层 receiver 槽。
|
|
||||||
let mut vm = Vm::new();
|
|
||||||
vm.install_module_table_for_test(Arc::new(vec![
|
|
||||||
Arc::new(CompiledModule::new()),
|
|
||||||
Arc::new(sub_module_254_with_r253_write()),
|
|
||||||
]));
|
|
||||||
vm.active_reg_limit = 254;
|
|
||||||
vm.regs[253] = JsValue::int(7);
|
|
||||||
vm.regs[254] = JsValue::int(8);
|
|
||||||
|
|
||||||
let inner_callee = vm.create_function_object(1, vm.current_gen, false, false, false, false);
|
|
||||||
let outer_native = native_function(&mut vm, native_nested_inline_254);
|
|
||||||
let result = vm
|
|
||||||
.call_function_sync(outer_native, JsValue::int(99), &[inner_callee])
|
|
||||||
.expect("native callback should succeed");
|
|
||||||
|
|
||||||
assert_eq!(result, JsValue::int(1), "内嵌 inline 回调后外层 receiver 槽必须保持原值");
|
|
||||||
assert_eq!(vm.regs[253], JsValue::int(7), "native 分支恢复后调用方 regs[253] 保持");
|
|
||||||
assert_eq!(vm.regs[254], JsValue::int(8), "native 分支恢复后调用方 regs[254] 保持");
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn push_bytecode_frame_param_overlap_reads_spill_first() {
|
|
||||||
// 实参源区间 regs[1..3) 与 callee 形参写入区 regs[2..4) 重叠(first < param_base):
|
|
||||||
// 压帧必须先拷 spill 实参区、形参再从 spill 区取源,避免边写形参边读实参
|
|
||||||
// 造成先写后读串值(形参 b 与 spill 实参区都取错)。
|
|
||||||
let mut vm = Vm::new();
|
|
||||||
// sub_modules[1] = callee:2 个形参,param_base=2(与调用方实参槽 2 重叠)
|
|
||||||
let mut callee_mod = CompiledModule::new();
|
|
||||||
callee_mod.n_args = 2;
|
|
||||||
callee_mod.param_base = 2;
|
|
||||||
callee_mod.n_registers = 5;
|
|
||||||
callee_mod.bytecode = Arc::from(vec![opcode::encode(opcode::OpCode::RETURN, 0, 0, 0)]);
|
|
||||||
vm.install_module_table_for_test(Arc::new(vec![Arc::new(CompiledModule::new()), Arc::new(callee_mod)]));
|
|
||||||
vm.active_reg_limit = 8;
|
|
||||||
// 调用方实参区 regs[1..3):arg0=10, arg1=20;regs[2] 同时是 callee 形参槽(param_base=2)
|
|
||||||
vm.regs[1] = JsValue::int(10);
|
|
||||||
vm.regs[2] = JsValue::int(20);
|
|
||||||
|
|
||||||
let callee = vm.create_function_object(1, vm.current_gen, false, false, false, false);
|
|
||||||
vm.push_bytecode_frame(
|
|
||||||
callee,
|
|
||||||
JsValue::undefined(),
|
|
||||||
super::FrameArgs::RegRange { first: 1, count: 2 },
|
|
||||||
None,
|
|
||||||
None,
|
|
||||||
JsValue::undefined(),
|
|
||||||
super::FrameContinuation::None,
|
|
||||||
0,
|
|
||||||
)
|
|
||||||
.expect("压帧成功");
|
|
||||||
// 形参从 spill 实参区取源:regs[2]=arg0=10,regs[3]=arg1=20(不得被先写覆盖)
|
|
||||||
assert_eq!(vm.regs[2], JsValue::int(10), "形参 a 应为实参 arg0");
|
|
||||||
assert_eq!(vm.regs[3], JsValue::int(20), "形参 b 应为实参 arg1(不受先写覆盖)");
|
|
||||||
// spill 实参区(arguments 对象源)保持原实参值
|
|
||||||
let frame = vm.frames.last().expect("压帧后应有帧");
|
|
||||||
let base = frame.arguments_base as usize;
|
|
||||||
assert_eq!(vm.spill_stack[base], JsValue::int(10), "spill 实参区 arg0");
|
|
||||||
assert_eq!(vm.spill_stack[base + 1], JsValue::int(20), "spill 实参区 arg1");
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn dispatch_new_expression_param_overlap_reads_spill_first() {
|
|
||||||
// NEW 收敛路径同款重叠几何(实参源 regs[1..3) 与 callee 形参写入区 regs[2..4)
|
|
||||||
// 重叠,first < param_base):经 NEW_EXPRESSION 入口压帧,形参与 spill 实参区
|
|
||||||
// (arguments 对象源)必须取原始实参值,不得先写后读串值。
|
|
||||||
let mut vm = Vm::new();
|
|
||||||
// sub_modules[1] = 构造器:2 个形参,param_base=2(与调用方实参槽 2 重叠)
|
|
||||||
let mut ctor_mod = CompiledModule::new();
|
|
||||||
ctor_mod.n_args = 2;
|
|
||||||
ctor_mod.param_base = 2;
|
|
||||||
ctor_mod.n_registers = 5;
|
|
||||||
ctor_mod.bytecode = Arc::from(vec![opcode::encode(opcode::OpCode::RETURN, 0, 0, 0)]);
|
|
||||||
vm.install_module_table_for_test(Arc::new(vec![Arc::new(CompiledModule::new()), Arc::new(ctor_mod)]));
|
|
||||||
vm.active_reg_limit = 8;
|
|
||||||
// NEW_EXPRESSION 指令:ext 低 8 位 = 实参个数 2,高 8 位 = 窗口 0(全量)
|
|
||||||
vm.bytecode = Arc::from(vec![opcode::encode(opcode::OpCode::NEW_EXPRESSION, 0, 0, 0), 2]);
|
|
||||||
vm.pc = 0;
|
|
||||||
// 调用方实参区 regs[1..3):arg0=10, arg1=20;regs[2] 同时是 callee 形参槽(param_base=2)
|
|
||||||
vm.regs[1] = JsValue::int(10);
|
|
||||||
vm.regs[2] = JsValue::int(20);
|
|
||||||
vm.regs[5] = vm.create_function_object(1, vm.current_gen, false, false, false, false);
|
|
||||||
|
|
||||||
vm.dispatch_new_expression(0, 5, 1).expect("NEW 压帧成功");
|
|
||||||
|
|
||||||
let frame = vm.frames.last().expect("压帧后应有帧");
|
|
||||||
// 形参从 spill 实参区取源:regs[2]=arg0=10,regs[3]=arg1=20(不得被先写覆盖)
|
|
||||||
assert_eq!(vm.regs[2], JsValue::int(10), "形参 a 应为实参 arg0");
|
|
||||||
assert_eq!(vm.regs[3], JsValue::int(20), "形参 b 应为实参 arg1(不受先写覆盖)");
|
|
||||||
// spill 实参区(arguments 对象源)保持原实参值
|
|
||||||
let base = frame.arguments_base as usize;
|
|
||||||
assert_eq!(vm.spill_stack[base], JsValue::int(10), "spill 实参区 arg0");
|
|
||||||
assert_eq!(vm.spill_stack[base + 1], JsValue::int(20), "spill 实参区 arg1");
|
|
||||||
// NEW 帧契约:构造结果寄存器与 constructed_this 随帧携带
|
|
||||||
assert_eq!(frame.construct_result_reg, Some(0), "构造结果写回 regs[0]");
|
|
||||||
assert!(
|
|
||||||
frame.constructed_this.is_some_and(|v| v.is_object()),
|
|
||||||
"基类构造路径 constructed_this 应为新对象"
|
|
||||||
);
|
|
||||||
assert!(!frame.is_derived_constructor, "普通函数非 derived 构造器");
|
|
||||||
}
|
|
||||||
|
|
||||||
fn units_of(s: &str) -> Vec<u16> {
|
fn units_of(s: &str) -> Vec<u16> {
|
||||||
s.chars().map(|c| c as u16).collect()
|
s.chars().map(|c| c as u16).collect()
|
||||||
|
|||||||
@@ -1118,3 +1118,79 @@ impl Vm {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use oxide_bytecode::module::CompiledModule;
|
||||||
|
use oxide_bytecode::opcode;
|
||||||
|
|
||||||
|
use crate::vm_state::ForOfEntry;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn for_of_close_pops_iterator_stack() {
|
||||||
|
let module = CompiledModule {
|
||||||
|
bytecode: Arc::from(vec![
|
||||||
|
opcode::encode(opcode::OpCode::FOR_OF_CLOSE, 0, 0, 0),
|
||||||
|
opcode::encode(opcode::OpCode::HALT, 0, 0, 0),
|
||||||
|
]),
|
||||||
|
n_registers: 1,
|
||||||
|
..CompiledModule::new()
|
||||||
|
};
|
||||||
|
let mut vm = Vm::new();
|
||||||
|
vm.iters.for_of_iters.push(ForOfEntry {
|
||||||
|
iterator: JsValue::undefined(),
|
||||||
|
last_result: JsValue::undefined(),
|
||||||
|
is_async: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
vm.run(&Arc::new(module))
|
||||||
|
.expect("FOR_OF_CLOSE should tolerate non-object sentinel");
|
||||||
|
|
||||||
|
assert!(vm.iters.for_of_iters.is_empty());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn dispatch_new_expression_param_overlap_reads_spill_first() {
|
||||||
|
// NEW 收敛路径同款重叠几何(实参源 regs[1..3) 与 callee 形参写入区 regs[2..4)
|
||||||
|
// 重叠,first < param_base):经 NEW_EXPRESSION 入口压帧,形参与 spill 实参区
|
||||||
|
// (arguments 对象源)必须取原始实参值,不得先写后读串值。
|
||||||
|
let mut vm = Vm::new();
|
||||||
|
// sub_modules[1] = 构造器:2 个形参,param_base=2(与调用方实参槽 2 重叠)
|
||||||
|
let mut ctor_mod = CompiledModule::new();
|
||||||
|
ctor_mod.n_args = 2;
|
||||||
|
ctor_mod.param_base = 2;
|
||||||
|
ctor_mod.n_registers = 5;
|
||||||
|
ctor_mod.bytecode = Arc::from(vec![opcode::encode(opcode::OpCode::RETURN, 0, 0, 0)]);
|
||||||
|
vm.install_module_table_for_test(Arc::new(vec![Arc::new(CompiledModule::new()), Arc::new(ctor_mod)]));
|
||||||
|
vm.active_reg_limit = 8;
|
||||||
|
// NEW_EXPRESSION 指令:ext 低 8 位 = 实参个数 2,高 8 位 = 窗口 0(全量)
|
||||||
|
vm.bytecode = Arc::from(vec![opcode::encode(opcode::OpCode::NEW_EXPRESSION, 0, 0, 0), 2]);
|
||||||
|
vm.pc = 0;
|
||||||
|
// 调用方实参区 regs[1..3):arg0=10, arg1=20;regs[2] 同时是 callee 形参槽(param_base=2)
|
||||||
|
vm.regs[1] = JsValue::int(10);
|
||||||
|
vm.regs[2] = JsValue::int(20);
|
||||||
|
vm.regs[5] = vm.create_function_object(1, vm.current_gen, false, false, false, false);
|
||||||
|
|
||||||
|
vm.dispatch_new_expression(0, 5, 1).expect("NEW 压帧成功");
|
||||||
|
|
||||||
|
let frame = vm.frames.last().expect("压帧后应有帧");
|
||||||
|
// 形参从 spill 实参区取源:regs[2]=arg0=10,regs[3]=arg1=20(不得被先写覆盖)
|
||||||
|
assert_eq!(vm.regs[2], JsValue::int(10), "形参 a 应为实参 arg0");
|
||||||
|
assert_eq!(vm.regs[3], JsValue::int(20), "形参 b 应为实参 arg1(不受先写覆盖)");
|
||||||
|
// spill 实参区(arguments 对象源)保持原实参值
|
||||||
|
let base = frame.arguments_base as usize;
|
||||||
|
assert_eq!(vm.spill_stack[base], JsValue::int(10), "spill 实参区 arg0");
|
||||||
|
assert_eq!(vm.spill_stack[base + 1], JsValue::int(20), "spill 实参区 arg1");
|
||||||
|
// NEW 帧契约:构造结果寄存器与 constructed_this 随帧携带
|
||||||
|
assert_eq!(frame.construct_result_reg, Some(0), "构造结果写回 regs[0]");
|
||||||
|
assert!(
|
||||||
|
frame.constructed_this.is_some_and(|v| v.is_object()),
|
||||||
|
"基类构造路径 constructed_this 应为新对象"
|
||||||
|
);
|
||||||
|
assert!(!frame.is_derived_constructor, "普通函数非 derived 构造器");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -867,3 +867,173 @@ impl Vm {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
use oxide_runtime_api::NativeResult;
|
||||||
|
use oxide_types::object::NativeFnPtr;
|
||||||
|
|
||||||
|
fn native_return_7(_vm: &mut Vm, _args: &[u8]) -> NativeResult {
|
||||||
|
NativeResult::Ok(JsValue::int(7))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn native_get_marker(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
||||||
|
let this_val = vm.reg(args[0]);
|
||||||
|
if !this_val.is_object() {
|
||||||
|
return NativeResult::Ok(JsValue::undefined());
|
||||||
|
}
|
||||||
|
let marker_si = vm.kernel_core.perm_interner().intern("marker").0;
|
||||||
|
let obj = unsafe { &*this_val.as_js_object_ptr() };
|
||||||
|
NativeResult::Ok(vm.resolve_property(obj, marker_si).unwrap_or(JsValue::undefined()))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn native_set_marker(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
||||||
|
let this_val = vm.reg(args[0]);
|
||||||
|
let value = vm.reg(args[1]);
|
||||||
|
if !this_val.is_object() {
|
||||||
|
return NativeResult::Ok(JsValue::undefined());
|
||||||
|
}
|
||||||
|
let marker_si = vm.kernel_core.perm_interner().intern("marker").0;
|
||||||
|
let obj = unsafe { &mut *this_val.as_js_object_ptr() };
|
||||||
|
vm.set_or_create_prop_value(obj, marker_si, value);
|
||||||
|
NativeResult::Ok(JsValue::undefined())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn native_function(vm: &mut Vm, f: crate::native::NativeFn) -> JsValue {
|
||||||
|
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 是 NativeFn 函数项,可作为 NativeFnPtr 存储。
|
||||||
|
obj.set_native_fn(Some(unsafe { NativeFnPtr::from_raw(f as *const ()) }));
|
||||||
|
JsValue::object(vm.alloc_object(obj) as *mut u8)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn plain_object(vm: &mut Vm) -> JsValue {
|
||||||
|
let proto = vm.session.builtin_world().object_proto.as_ptr() as *mut JsObject;
|
||||||
|
let obj = JsObject::new_empty(oxide_kernel::shape_forge::EMPTY_SHAPE_ID, JsValue::from_js_object(proto));
|
||||||
|
JsValue::object(vm.alloc_object(obj) as *mut u8)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn add_accessor(vm: &mut Vm, obj_val: JsValue, name: &str, get: JsValue, set: JsValue) {
|
||||||
|
let si = vm.kernel_core.perm_interner().intern(name).0;
|
||||||
|
let obj = unsafe { &mut *obj_val.as_js_object_ptr() };
|
||||||
|
let shape_id = vm.kernel_core.shape_forge().make_shape(obj.shape_id(), si);
|
||||||
|
obj.set_shape_id(shape_id);
|
||||||
|
let pos = obj.push_prop(JsValue::undefined());
|
||||||
|
obj.set_accessor_meta(pos, get, set, PropAttributes::DEFAULT_DATA);
|
||||||
|
obj.bump_generation();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_data(vm: &mut Vm, obj_val: JsValue, name: &str, val: JsValue) {
|
||||||
|
let si = vm.kernel_core.perm_interner().intern(name).0;
|
||||||
|
let obj = unsafe { &mut *obj_val.as_js_object_ptr() };
|
||||||
|
vm.set_or_create_prop_value(obj, si, val);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn ordinary_get_calls_own_native_getter() {
|
||||||
|
let mut vm = Vm::new();
|
||||||
|
let obj_val = plain_object(&mut vm);
|
||||||
|
let getter = native_function(&mut vm, native_return_7);
|
||||||
|
add_accessor(&mut vm, obj_val, "x", getter, JsValue::undefined());
|
||||||
|
|
||||||
|
let x_si = vm.kernel_core.perm_interner().intern("x").0;
|
||||||
|
let obj = unsafe { &*obj_val.as_js_object_ptr() };
|
||||||
|
let value = vm.ordinary_get(obj, x_si, obj_val).expect("getter");
|
||||||
|
assert_eq!(value, JsValue::int(7));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn ordinary_set_calls_own_native_setter() {
|
||||||
|
let mut vm = Vm::new();
|
||||||
|
let obj_val = plain_object(&mut vm);
|
||||||
|
let setter = native_function(&mut vm, native_set_marker);
|
||||||
|
add_accessor(&mut vm, obj_val, "x", JsValue::undefined(), setter);
|
||||||
|
|
||||||
|
let x_si = vm.kernel_core.perm_interner().intern("x").0;
|
||||||
|
let obj = unsafe { &mut *obj_val.as_js_object_ptr() };
|
||||||
|
vm.ordinary_set(obj, x_si, JsValue::int(9), obj_val, true).expect("setter");
|
||||||
|
|
||||||
|
let marker_si = vm.kernel_core.perm_interner().intern("marker").0;
|
||||||
|
let obj = unsafe { &*obj_val.as_js_object_ptr() };
|
||||||
|
assert_eq!(vm.resolve_property(obj, marker_si), Some(JsValue::int(9)));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn inherited_getter_uses_original_receiver() {
|
||||||
|
let mut vm = Vm::new();
|
||||||
|
let proto_val = plain_object(&mut vm);
|
||||||
|
let child_val = plain_object(&mut vm);
|
||||||
|
let getter = native_function(&mut vm, native_get_marker);
|
||||||
|
add_accessor(&mut vm, proto_val, "x", getter, JsValue::undefined());
|
||||||
|
set_data(&mut vm, child_val, "marker", JsValue::int(42));
|
||||||
|
unsafe {
|
||||||
|
(*child_val.as_js_object_ptr()).set_proto(proto_val).expect("proto");
|
||||||
|
}
|
||||||
|
|
||||||
|
let x_si = vm.kernel_core.perm_interner().intern("x").0;
|
||||||
|
let child = unsafe { &*child_val.as_js_object_ptr() };
|
||||||
|
let value = vm.ordinary_get(child, x_si, child_val).expect("getter");
|
||||||
|
assert_eq!(value, JsValue::int(42));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn inherited_setter_uses_original_receiver() {
|
||||||
|
let mut vm = Vm::new();
|
||||||
|
let proto_val = plain_object(&mut vm);
|
||||||
|
let child_val = plain_object(&mut vm);
|
||||||
|
let setter = native_function(&mut vm, native_set_marker);
|
||||||
|
add_accessor(&mut vm, proto_val, "x", JsValue::undefined(), setter);
|
||||||
|
unsafe {
|
||||||
|
(*child_val.as_js_object_ptr()).set_proto(proto_val).expect("proto");
|
||||||
|
}
|
||||||
|
|
||||||
|
let x_si = vm.kernel_core.perm_interner().intern("x").0;
|
||||||
|
let child = unsafe { &mut *child_val.as_js_object_ptr() };
|
||||||
|
vm.ordinary_set(child, x_si, JsValue::int(12), child_val, true).expect("setter");
|
||||||
|
|
||||||
|
let marker_si = vm.kernel_core.perm_interner().intern("marker").0;
|
||||||
|
let child = unsafe { &*child_val.as_js_object_ptr() };
|
||||||
|
let proto = unsafe { &*proto_val.as_js_object_ptr() };
|
||||||
|
assert_eq!(vm.resolve_property(child, marker_si), Some(JsValue::int(12)));
|
||||||
|
assert_eq!(vm.resolve_property(proto, marker_si), None);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn deep_inherited_setter_uses_original_receiver() {
|
||||||
|
let mut vm = Vm::new();
|
||||||
|
let grand_proto_val = plain_object(&mut vm);
|
||||||
|
let proto_val = plain_object(&mut vm);
|
||||||
|
let child_val = plain_object(&mut vm);
|
||||||
|
let setter = native_function(&mut vm, native_set_marker);
|
||||||
|
add_accessor(&mut vm, grand_proto_val, "x", JsValue::undefined(), setter);
|
||||||
|
unsafe {
|
||||||
|
(*proto_val.as_js_object_ptr()).set_proto(grand_proto_val).expect("proto");
|
||||||
|
(*child_val.as_js_object_ptr()).set_proto(proto_val).expect("proto");
|
||||||
|
}
|
||||||
|
|
||||||
|
let x_si = vm.kernel_core.perm_interner().intern("x").0;
|
||||||
|
let child = unsafe { &mut *child_val.as_js_object_ptr() };
|
||||||
|
vm.ordinary_set(child, x_si, JsValue::int(15), child_val, true).expect("setter");
|
||||||
|
|
||||||
|
let marker_si = vm.kernel_core.perm_interner().intern("marker").0;
|
||||||
|
let child = unsafe { &*child_val.as_js_object_ptr() };
|
||||||
|
assert_eq!(vm.resolve_property(child, marker_si), Some(JsValue::int(15)));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn ordinary_data_property_still_reads_and_writes_without_meta() {
|
||||||
|
let mut vm = Vm::new();
|
||||||
|
let obj_val = plain_object(&mut vm);
|
||||||
|
set_data(&mut vm, obj_val, "x", JsValue::int(1));
|
||||||
|
|
||||||
|
let x_si = vm.kernel_core.perm_interner().intern("x").0;
|
||||||
|
let obj = unsafe { &mut *obj_val.as_js_object_ptr() };
|
||||||
|
assert!(!obj.has_prop_meta());
|
||||||
|
assert_eq!(vm.ordinary_get(obj, x_si, obj_val).expect("get"), JsValue::int(1));
|
||||||
|
vm.ordinary_set(obj, x_si, JsValue::int(2), obj_val, true).expect("set");
|
||||||
|
assert_eq!(vm.ordinary_get(obj, x_si, obj_val).expect("get"), JsValue::int(2));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1698,4 +1698,85 @@ mod tests {
|
|||||||
let te = run_source(&mut vm, "try { Object(1n) - 1 } catch(e) { e.name }");
|
let te = run_source(&mut vm, "try { Object(1n) - 1 } catch(e) { e.name }");
|
||||||
assert_eq!(vm.lookup_str(te).as_deref(), Some("TypeError"));
|
assert_eq!(vm.lookup_str(te).as_deref(), Some("TypeError"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn reset_clears_runtime_state_like_rerun() {
|
||||||
|
let mut vm = Vm::new();
|
||||||
|
vm.regs[1] = JsValue::int(7);
|
||||||
|
vm.pc = 3;
|
||||||
|
vm.frames.push(crate::vm::CallFrame {
|
||||||
|
return_addr: 1,
|
||||||
|
function_name: 0,
|
||||||
|
caller_reg_limit: 2,
|
||||||
|
caller_active_reg_limit: 2,
|
||||||
|
saved_reg_offset: 0,
|
||||||
|
spill_offset: 0,
|
||||||
|
arguments_base: 0,
|
||||||
|
arguments_count: 0,
|
||||||
|
saved_this: JsValue::undefined(),
|
||||||
|
saved_new_target: JsValue::undefined(),
|
||||||
|
callee: JsValue::undefined(),
|
||||||
|
construct_result_reg: None,
|
||||||
|
strict: false,
|
||||||
|
constructed_this: None,
|
||||||
|
is_derived_constructor: false,
|
||||||
|
super_called: false,
|
||||||
|
continuation: crate::vm::FrameContinuation::None,
|
||||||
|
});
|
||||||
|
vm.save_stack.push(JsValue::undefined());
|
||||||
|
vm.iters
|
||||||
|
.for_in_iters
|
||||||
|
.push(std::ptr::dangling_mut::<crate::vm::ForInIter<'static>>());
|
||||||
|
vm.iters.for_of_iters.push(crate::vm_state::ForOfEntry {
|
||||||
|
iterator: JsValue::undefined(),
|
||||||
|
last_result: JsValue::undefined(),
|
||||||
|
is_async: false,
|
||||||
|
});
|
||||||
|
vm.saved_bytecode_stack.push(Arc::from(vec![oxide_bytecode::opcode::encode(
|
||||||
|
oxide_bytecode::opcode::OpCode::HALT,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
)]));
|
||||||
|
vm.saved_immutables_stack
|
||||||
|
.push(std::ptr::slice_from_raw_parts(std::ptr::null(), 0));
|
||||||
|
vm.try_stack.push(crate::vm::TryHandler {
|
||||||
|
catch_pc: Some(1),
|
||||||
|
finally_pc: None,
|
||||||
|
finally_active: false,
|
||||||
|
frame_depth: 0,
|
||||||
|
for_of_depth: 0,
|
||||||
|
});
|
||||||
|
vm.exception_value = Some(JsValue::int(2));
|
||||||
|
vm.pending_exception = Some(JsValue::int(3));
|
||||||
|
vm.pending_error_kind = Some("TypeError");
|
||||||
|
|
||||||
|
vm.reset();
|
||||||
|
|
||||||
|
assert_eq!(vm.pc, 0);
|
||||||
|
assert!(vm.frames.is_empty());
|
||||||
|
assert!(vm.save_stack.is_empty());
|
||||||
|
assert!(vm.iters.for_in_iters.is_empty());
|
||||||
|
assert!(vm.iters.for_of_iters.is_empty());
|
||||||
|
assert!(vm.saved_bytecode_stack.is_empty());
|
||||||
|
assert!(vm.saved_immutables_stack.is_empty());
|
||||||
|
assert!(vm.try_stack.is_empty());
|
||||||
|
assert!(vm.exception_value.is_none());
|
||||||
|
assert!(vm.pending_exception.is_none());
|
||||||
|
assert!(vm.pending_error_kind.is_none());
|
||||||
|
assert!(vm.bytecode.is_empty());
|
||||||
|
assert!(vm.immutables().is_empty());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn full_reset_clears_symbol_state() {
|
||||||
|
let mut vm = Vm::new();
|
||||||
|
vm.symbols.intern(Some("shared".to_string()));
|
||||||
|
|
||||||
|
vm.full_reset();
|
||||||
|
|
||||||
|
assert_eq!(vm.symbols.symbol_counter, 0);
|
||||||
|
assert!(vm.symbols.symbol_descriptions.is_empty());
|
||||||
|
assert!(vm.symbols.symbol_registry.is_empty());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,25 @@
|
|||||||
|
//! 未实装 profile 族 opcodes 的显式失败钉:run 遇 PROFILE_SHAPE 须返回
|
||||||
|
//! 显式错误而非静默按数据指令消费(手工 CompiledModule 直达,不经编译器)。
|
||||||
|
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use oxide_bytecode::module::CompiledModule;
|
||||||
|
use oxide_bytecode::opcode;
|
||||||
|
use oxide_vm::vm::Vm;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn unimplemented_profile_opcode_fails_explicitly() {
|
||||||
|
let module = CompiledModule {
|
||||||
|
bytecode: Arc::from(vec![
|
||||||
|
opcode::encode(opcode::OpCode::PROFILE_SHAPE, 0, 0, 0),
|
||||||
|
opcode::encode(opcode::OpCode::HALT, 0, 0, 0),
|
||||||
|
]),
|
||||||
|
n_registers: 1,
|
||||||
|
..CompiledModule::new()
|
||||||
|
};
|
||||||
|
let mut vm = Vm::new();
|
||||||
|
let err = vm
|
||||||
|
.run(&Arc::new(module))
|
||||||
|
.expect_err("unimplemented opcode should fail explicitly");
|
||||||
|
assert_eq!(err, "opcode PROFILE_SHAPE not yet implemented");
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user