From 7a5b1b2d06946a027cd58fe49defa1b1a8e53054 Mon Sep 17 00:00:00 2001 From: pjh456 <147148383@qq.com> Date: Wed, 16 Sep 2026 17:07:29 +0000 Subject: [PATCH] =?UTF-8?q?refactor:=20vm.rs=20=E5=86=85=E8=81=94=E6=B5=8B?= =?UTF-8?q?=E8=AF=95=E6=8C=89=E5=B1=9E=E4=B8=BB=E6=A8=A1=E5=9D=97=E5=BD=92?= =?UTF-8?q?=E4=BD=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- crates/oxide_vm/src/ic_helper.rs | 32 + crates/oxide_vm/src/vm/call.rs | 213 +++++++ crates/oxide_vm/src/vm/mod.rs | 559 ------------------ crates/oxide_vm/src/vm_dispatch_misc.rs | 76 +++ crates/oxide_vm/src/vm_props.rs | 170 ++++++ crates/oxide_vm/src/vm_support.rs | 81 +++ .../dispatch_unimplemented_opcode_tests.rs | 25 + 7 files changed, 597 insertions(+), 559 deletions(-) create mode 100644 crates/oxide_vm/tests/dispatch_unimplemented_opcode_tests.rs diff --git a/crates/oxide_vm/src/ic_helper.rs b/crates/oxide_vm/src/ic_helper.rs index eb30d048..7416779e 100644 --- a/crates/oxide_vm/src/ic_helper.rs +++ b/crates/oxide_vm/src/ic_helper.rs @@ -387,4 +387,36 @@ mod tests { assert_eq!(bytecode[1], 0x0000_0040, "BREAK ext 字保持原值"); 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); + } } diff --git a/crates/oxide_vm/src/vm/call.rs b/crates/oxide_vm/src/vm/call.rs index 9b6e028a..1d35fb1c 100644 --- a/crates/oxide_vm/src/vm/call.rs +++ b/crates/oxide_vm/src/vm/call.rs @@ -278,3 +278,216 @@ impl Vm { 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 = (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 = (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 = (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"); + } +} diff --git a/crates/oxide_vm/src/vm/mod.rs b/crates/oxide_vm/src/vm/mod.rs index 1364d192..bf8e2d21 100644 --- a/crates/oxide_vm/src/vm/mod.rs +++ b/crates/oxide_vm/src/vm/mod.rs @@ -14,8 +14,6 @@ use smallvec::SmallVec; pub use crate::bindings::init_kernel_builtins; use crate::native::NativeFn; use crate::vm_debug; -#[cfg(test)] -use crate::vm_state::ForOfEntry; use crate::vm_state::{GcState, IterState, ProfilingState, SymbolState}; use oxide_kernel::kernel::{KernelCore, KernelSession}; use oxide_types::error::JsErrorKind; @@ -428,564 +426,7 @@ impl Vm { #[cfg(test)] mod tests { 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 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::>()); - 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 = (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 = (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 = (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 { s.chars().map(|c| c as u16).collect() diff --git a/crates/oxide_vm/src/vm_dispatch_misc.rs b/crates/oxide_vm/src/vm_dispatch_misc.rs index ae27a206..92060b77 100644 --- a/crates/oxide_vm/src/vm_dispatch_misc.rs +++ b/crates/oxide_vm/src/vm_dispatch_misc.rs @@ -1118,3 +1118,79 @@ impl Vm { 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 构造器"); + } +} diff --git a/crates/oxide_vm/src/vm_props.rs b/crates/oxide_vm/src/vm_props.rs index 966a331d..4a266c11 100644 --- a/crates/oxide_vm/src/vm_props.rs +++ b/crates/oxide_vm/src/vm_props.rs @@ -867,3 +867,173 @@ impl Vm { 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)); + } +} diff --git a/crates/oxide_vm/src/vm_support.rs b/crates/oxide_vm/src/vm_support.rs index e1c5a53a..ccc1ca19 100644 --- a/crates/oxide_vm/src/vm_support.rs +++ b/crates/oxide_vm/src/vm_support.rs @@ -1698,4 +1698,85 @@ mod tests { let te = run_source(&mut vm, "try { Object(1n) - 1 } catch(e) { e.name }"); 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::>()); + 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()); + } } diff --git a/crates/oxide_vm/tests/dispatch_unimplemented_opcode_tests.rs b/crates/oxide_vm/tests/dispatch_unimplemented_opcode_tests.rs new file mode 100644 index 00000000..c092dc4f --- /dev/null +++ b/crates/oxide_vm/tests/dispatch_unimplemented_opcode_tests.rs @@ -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"); +}