diff --git a/Cargo.lock b/Cargo.lock index d68943f1..f6d551b4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1160,7 +1160,7 @@ dependencies = [ [[package]] name = "oxide_builtins" -version = "0.13.21" +version = "0.13.22" dependencies = [ "chrono", "fast-float", @@ -1185,7 +1185,7 @@ dependencies = [ [[package]] name = "oxide_bytecode" -version = "0.13.21" +version = "0.13.22" dependencies = [ "num-bigint", "oxide_log", @@ -1193,7 +1193,7 @@ dependencies = [ [[package]] name = "oxide_cfg" -version = "0.13.21" +version = "0.13.22" dependencies = [ "oxide_bytecode", "oxide_emit", @@ -1204,7 +1204,7 @@ dependencies = [ [[package]] name = "oxide_cli" -version = "0.13.21" +version = "0.13.22" dependencies = [ "ansi_term", "bumpalo", @@ -1226,7 +1226,7 @@ dependencies = [ [[package]] name = "oxide_code_cache" -version = "0.13.21" +version = "0.13.22" dependencies = [ "lru", "oxide_bytecode", @@ -1235,7 +1235,7 @@ dependencies = [ [[package]] name = "oxide_compiler" -version = "0.13.21" +version = "0.13.22" dependencies = [ "oxide_bytecode", "oxide_cfg", @@ -1255,7 +1255,7 @@ dependencies = [ [[package]] name = "oxide_dce" -version = "0.13.21" +version = "0.13.22" dependencies = [ "oxide_bytecode", "oxide_cfg", @@ -1269,7 +1269,7 @@ dependencies = [ [[package]] name = "oxide_emit" -version = "0.13.21" +version = "0.13.22" dependencies = [ "num-bigint", "oxc_allocator", @@ -1284,7 +1284,7 @@ dependencies = [ [[package]] name = "oxide_engine" -version = "0.13.21" +version = "0.13.22" dependencies = [ "oxide_bytecode", "oxide_compiler", @@ -1296,7 +1296,7 @@ dependencies = [ [[package]] name = "oxide_ir" -version = "0.13.21" +version = "0.13.22" dependencies = [ "oxide_bytecode", "oxide_log", @@ -1305,7 +1305,7 @@ dependencies = [ [[package]] name = "oxide_kernel" -version = "0.13.21" +version = "0.13.22" dependencies = [ "bumpalo", "dashmap", @@ -1319,7 +1319,7 @@ dependencies = [ [[package]] name = "oxide_liveness" -version = "0.13.21" +version = "0.13.22" dependencies = [ "oxide_bytecode", "oxide_cfg", @@ -1331,7 +1331,7 @@ dependencies = [ [[package]] name = "oxide_log" -version = "0.13.21" +version = "0.13.22" dependencies = [ "tracing", "tracing-appender", @@ -1340,7 +1340,7 @@ dependencies = [ [[package]] name = "oxide_parser" -version = "0.13.21" +version = "0.13.22" dependencies = [ "oxc_allocator", "oxc_ast", @@ -1355,7 +1355,7 @@ dependencies = [ [[package]] name = "oxide_regalloc" -version = "0.13.21" +version = "0.13.22" dependencies = [ "oxide_bytecode", "oxide_cfg", @@ -1371,7 +1371,7 @@ dependencies = [ [[package]] name = "oxide_runtime_api" -version = "0.13.21" +version = "0.13.22" dependencies = [ "num-bigint", "num-traits", @@ -1384,7 +1384,7 @@ dependencies = [ [[package]] name = "oxide_test262" -version = "0.13.21" +version = "0.13.22" dependencies = [ "oxide_bytecode", "oxide_compiler", @@ -1402,7 +1402,7 @@ dependencies = [ [[package]] name = "oxide_types" -version = "0.13.21" +version = "0.13.22" dependencies = [ "bumpalo", "dashmap", @@ -1415,7 +1415,7 @@ dependencies = [ [[package]] name = "oxide_vm" -version = "0.13.21" +version = "0.13.22" dependencies = [ "bumpalo", "chrono", diff --git a/Cargo.toml b/Cargo.toml index 23c11b5b..15171b84 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,7 +23,7 @@ members = [ ] [workspace.package] -version = "0.13.21" +version = "0.13.22" edition = "2021" license = "MIT" rust-version = "1.80" diff --git a/crates/oxide_builtins/src/object.rs b/crates/oxide_builtins/src/object.rs index af6c6ce7..e4750724 100644 --- a/crates/oxide_builtins/src/object.rs +++ b/crates/oxide_builtins/src/object.rs @@ -1,5 +1,6 @@ use oxide_kernel::shape_forge::{ShapeForge, EMPTY_SHAPE_ID}; use oxide_kernel::string_forge::PermInterner; +use oxide_types::arguments_map::ArgumentsMapState; use oxide_types::object::{JsObject, PropAttributes, PropMetaEntry}; use oxide_types::private_key::{ decode_symbol_key, encode_symbol_key, int_key_value, is_int_key, is_private_name_key, is_symbol_key, make_int_key, @@ -365,6 +366,33 @@ pub fn delete_own_property_outcome( } } + // mapped arguments exotic [[Delete]]:整数索引且映射存活时,属性不可配置返回 + // NonConfigurable(严格抛 TypeError、sloppy 返回 false);可配置时移除映射并 + // 落穿普通删除路径。accessor 属性不移除映射(无参数寄存器同步)。 + if obj.is_arguments_obj() { + let state_ptr = obj.native_data() as *mut ArgumentsMapState; + if !state_ptr.is_null() { + // SAFETY: native_data 由创建期 Box::into_raw 分配,生命周期与对象一致。 + let state = unsafe { &mut *state_ptr }; + if vm.arguments_mapping_alive(state) { + if let Some(index) = array_index_of(vm, key_si) { + if (index as u16) < state.param_count && state.is_mapped(index as u16) { + let pos = vm.get_own_property_slot(obj, key_si); + let meta = pos.and_then(|p| obj.prop_meta_at(p)); + let is_accessor = meta.is_some_and(|m| m.is_accessor); + let configurable = meta.map(|m| m.attributes.configurable()).unwrap_or(true); + if !configurable { + return Ok(DeleteOutcome::NonConfigurable); + } + if !is_accessor { + state.unmap(index as u16); + } + } + } + } + } + } + // 数组下标元素在元素区,不参与 shape 链,单独删除(保持 length 不变)。 if obj.is_array() { if let Some(index) = array_index_of(vm, key_si) { @@ -955,6 +983,38 @@ pub(crate) fn define_from_descriptor( } } + // mapped arguments exotic [[DefineOwnProperty]]:整数索引且映射存活时,旧描述符 + // 可写且新描述符不可写则移除映射,新描述符含 value 则写参数寄存器;随后落穿 + // 普通 define 路径(描述符收窄校验由 define_data_property 承担)。 + if unsafe { &*obj_ptr }.is_arguments_obj() { + let state_ptr = unsafe { &*obj_ptr }.native_data() as *mut ArgumentsMapState; + if !state_ptr.is_null() { + // SAFETY: native_data 由创建期 Box::into_raw 分配,生命周期与对象一致。 + let state = unsafe { &mut *state_ptr }; + if vm.arguments_mapping_alive(state) { + if let Some(index) = array_index_of(vm, key_si) { + if (index as u16) < state.param_count && state.is_mapped(index as u16) { + let old_writable = existing_meta.map(|m| m.attributes.writable()).unwrap_or(true); + let new_writable = if has_existing { + writable_field + .map(oxide_runtime_api::to_boolean) + .unwrap_or_else(|| existing_meta.map(|m| m.attributes.writable()).unwrap_or(false)) + } else { + writable_field.map(oxide_runtime_api::to_boolean).unwrap_or(false) + }; + if old_writable && !new_writable { + state.unmap(index as u16); + } + if let Some(v) = value_field { + let reg = (state.param_base as u16 + index as u16) as u8; + vm.set_reg(reg, v); + } + } + } + } + } + } + let obj = unsafe { &mut *obj_ptr }; if has_accessor { vm.define_accessor_property(obj, key_si, get, set, PropAttributes::new(false, enumerable, configurable)) diff --git a/crates/oxide_runtime_api/src/host.rs b/crates/oxide_runtime_api/src/host.rs index 577b74fe..b1054bf0 100644 --- a/crates/oxide_runtime_api/src/host.rs +++ b/crates/oxide_runtime_api/src/host.rs @@ -253,6 +253,12 @@ pub trait VmHost { /// 守卫抛 TypeError / 失败缓存原值重抛),触发失败以 `Err` 返回。 fn has_property(&mut self, obj: &JsObject, prop_name_si: u32) -> Result; fn get_own_property_slot(&self, obj: &JsObject, prop_name_si: u32) -> Option; + /// 判定 mapped arguments 映射是否存活:创建帧仍在帧栈上且帧身份匹配。 + /// + /// # 边界与前提 + /// - `state` 为 arguments 对象 `native_data` 中的同步状态盒;`frame_depth` + /// 为创建帧下标,帧弹出后映射失效。 + fn arguments_mapping_alive(&self, state: &oxide_types::arguments_map::ArgumentsMapState) -> bool; // 属性访问 fn ordinary_get(&mut self, obj: &JsObject, prop_name_si: u32, receiver: JsValue) -> Result; diff --git a/crates/oxide_types/src/arguments_map.rs b/crates/oxide_types/src/arguments_map.rs new file mode 100644 index 00000000..24116798 --- /dev/null +++ b/crates/oxide_types/src/arguments_map.rs @@ -0,0 +1,39 @@ +//! mapped arguments 对象同步状态盒:参数寄存器与 arguments 对象存储值双向同步的 +//! 创建期帧身份与逐索引映射存活位图。 + +/// mapped arguments 对象同步状态盒(存于 `native_data`,无 GC 边)。 +/// +/// 承载参数寄存器基址、形参数、逐索引映射存活位图与创建期帧身份,供参数寄存器 +/// 与 arguments 对象存储值的双向同步(exotic `[[Get]]`/`[[Set]]`/`[[DefineOwnProperty]]`/ +/// `[[Delete]]` 四臂共用)。 +pub struct ArgumentsMapState { + /// 首形参寄存器下标(`CompiledModule.param_base`)。 + pub param_base: u8, + /// 形参数(`[[ParameterMap]]`,`CompiledModule.n_args`)。 + pub param_count: u16, + /// 逐索引映射存活位图:bit i = 1 表示索引 i 仍映射。长度 = ceil(param_count / 64), + /// 构造期全 1(尾部多余位清零)。 + pub mapped_mask: Vec, + /// 创建帧在帧栈中的下标(创建期 `Vm.frames.len() - 1`)。 + pub frame_depth: u32, + /// 创建期帧身份(单调递增 `frame_id`)。 + pub frame_id: u64, +} + +impl ArgumentsMapState { + /// 索引 `index` 的映射是否存活(位图对应位为 1)。 + pub fn is_mapped(&self, index: u16) -> bool { + let word = (index as usize) / 64; + let bit = (index as usize) % 64; + word < self.mapped_mask.len() && (self.mapped_mask[word] >> bit) & 1 == 1 + } + + /// 移除索引 `index` 的映射(清位)。 + pub fn unmap(&mut self, index: u16) { + let word = (index as usize) / 64; + let bit = (index as usize) % 64; + if word < self.mapped_mask.len() { + self.mapped_mask[word] &= !(1u64 << bit); + } + } +} diff --git a/crates/oxide_types/src/lib.rs b/crates/oxide_types/src/lib.rs index 8be6c032..542551e8 100644 --- a/crates/oxide_types/src/lib.rs +++ b/crates/oxide_types/src/lib.rs @@ -1,5 +1,7 @@ #![doc = "OxideJS 共享核心类型:ECMAScript 值、对象模型、形状存储、内存抽象与运行时错误类型。"] +/// mapped arguments 对象同步状态盒(参数寄存器与 arguments 存储值双向同步)。 +pub mod arguments_map; /// 运行时错误类型([`JsError`] / [`JsErrorKind`])。 pub mod error; /// 内存抽象:持久指针、arena 分配(epoch)与持久堆。 diff --git a/crates/oxide_vm/src/arguments_gc.rs b/crates/oxide_vm/src/arguments_gc.rs new file mode 100644 index 00000000..14e3d881 --- /dev/null +++ b/crates/oxide_vm/src/arguments_gc.rs @@ -0,0 +1,35 @@ +//! mapped arguments 同步状态盒的 GC 支撑:size 核算与 drop 释放。 +//! +//! 状态盒存于对象 `native_data`(`Box::into_raw`),无引用边(位图与帧身份均为 +//! 原始值),仅 size/drop 链消费。 + +use std::mem::size_of; + +use oxide_types::arguments_map::ArgumentsMapState; +use oxide_types::object::JsObject; + +/// 只读核算 mapped arguments 同步状态盒字节(不释放),供 GC 账目核算。 +pub(crate) fn arguments_native_size(obj: &JsObject) -> u64 { + if !obj.is_arguments_obj() { + return 0; + } + let ptr = obj.native_data() as *mut ArgumentsMapState; + if ptr.is_null() { + return 0; + } + // SAFETY: 通过裸指针读取 capacity 字段,不解引用整个 Box(不移动/释放)。 + unsafe { size_of::() as u64 + (*ptr).mapped_mask.capacity() as u64 * size_of::() as u64 } +} + +/// 释放 mapped arguments 同步状态盒(对象被 GC 回收时),返回释放字节数。 +pub(crate) fn drop_arguments_native(obj: &JsObject) -> u64 { + let bytes = arguments_native_size(obj); + if bytes == 0 { + return 0; + } + let ptr = obj.native_data() as *mut ArgumentsMapState; + // SAFETY: ptr 非空(arguments_native_size 已验证),Box::from_raw 恰好释放一次。 + let state = unsafe { Box::from_raw(ptr) }; + drop(state); + bytes +} diff --git a/crates/oxide_vm/src/dispatch/misc.rs b/crates/oxide_vm/src/dispatch/misc.rs index 4474972a..3ab222d7 100644 --- a/crates/oxide_vm/src/dispatch/misc.rs +++ b/crates/oxide_vm/src/dispatch/misc.rs @@ -2,6 +2,7 @@ use crate::vm::Vm; use crate::{vm_error, vm_trace}; use oxide_bytecode::opcode; use oxide_kernel::shape_forge::EMPTY_SHAPE_ID; +use oxide_types::arguments_map::ArgumentsMapState; use oxide_types::object::{JsObject, PropAttributes}; use oxide_types::private_key::{encode_symbol_key, make_int_key}; use oxide_types::value::{JsType, JsValue}; @@ -153,9 +154,63 @@ impl Vm { } } self.regs[rd] = self.regs[a]; + // mapped arguments 参数直写同步:目标寄存器是创建帧的存活映射参数时, + // 把新值回写 arguments 存储值(读路径权威源),嵌套调用覆盖寄存器后 + // 存储值仍正确。 + self.sync_mapped_arguments_param(rd); Ok(false) } + /// mapped arguments 参数寄存器直写同步:目标寄存器是顶帧 mapped arguments 的 + /// 存活映射参数时,把新值回写 arguments 对象存储值(读路径权威源)。 + /// + /// # 边界与前提 + /// - 仅顶帧带 mapped arguments 对象(`frame.arguments_obj` 非 undefined)时 + /// 生效;其余帧首检即返回(热路径零开销)。 + /// - 映射失效(创建帧已弹出)或索引已 unmapped(收窄不可写 / 删除)时不同步。 + /// + /// # 副作用 + /// - 写 arguments 对象存储值槽(shape 槽已存在,无 shape 变更)。 + pub(crate) fn sync_mapped_arguments_param(&mut self, rd: usize) { + let Some(frame) = self.frames.last() else { + return; + }; + if frame.arguments_obj.is_undefined() { + return; + } + let obj_ptr = frame.arguments_obj.as_js_object_ptr(); + if obj_ptr.is_null() { + return; + } + // SAFETY: arguments_obj 是 GC 根(帧遍历期标记),session 内指针有效。 + if !unsafe { (*obj_ptr).is_arguments_obj() } { + return; + } + let state_ptr = unsafe { (*obj_ptr).native_data() } as *const ArgumentsMapState; + if state_ptr.is_null() { + return; + } + // SAFETY: native_data 由创建期 Box::into_raw 分配,生命周期与对象一致。 + let state = unsafe { &*state_ptr }; + if !self.arguments_mapping_alive(state) { + return; + } + let param_base = state.param_base as usize; + let param_count = state.param_count as usize; + if rd < param_base || rd >= param_base + param_count { + return; + } + let index = (rd - param_base) as u16; + if !state.is_mapped(index) { + return; + } + let si = make_int_key(index as u32); + let val = self.regs[rd]; + // SAFETY: obj_ptr 是有效对象指针(GC 根)。 + let obj_mut = unsafe { &mut *obj_ptr }; + self.set_or_create_prop_value(obj_mut, si, val); + } + /// SPILL:`regs[rd]` → `spill_stack[帧基址 + slot]`。 /// /// ext 字为 slot(u16);帧基址 = 当前 CallFrame.spill_offset(顶层模块无帧为 0)。 @@ -287,6 +342,40 @@ impl Vm { let obj = unsafe { &mut *obj_ptr }; obj.type_tag = JsObject::OBJ_TYPE_ARGUMENTS; + // mapped 判定:sloppy 且 simple(strict ‖ !simple → unmapped)。mapped 时 + // 建同步状态盒(存 native_data):参数寄存器与 arguments 存储值双向同步, + // 帧身份经 (帧下标, 帧身份) 判定创建帧是否仍在栈上。unmapped 时 native_data + // 保持 null,exotic 四臂短路,行为与现树一致。 + let strict = self.current_strict(); + let simple = self.active_module().is_some_and(|m| m.has_simple_params); + if !strict && simple { + if let (Some(frame_id), Some(module)) = (self.frames.last().map(|f| f.frame_id), self.active_module()) { + let param_base = module.param_base; + let param_count = module.n_args as u16; + let words = (param_count as usize).div_ceil(64).max(1); + let mut mapped_mask = vec![u64::MAX; words]; + let valid_bits = param_count as usize % 64; + if valid_bits != 0 { + mapped_mask[words - 1] &= (1u64 << valid_bits) - 1; + } else if param_count == 0 { + mapped_mask[0] = 0; + } + let state = ArgumentsMapState { + param_base, + param_count, + mapped_mask, + frame_depth: (self.frames.len() - 1) as u32, + frame_id, + }; + obj.set_native_data(Box::into_raw(Box::new(state)) as *mut u8); + // 帧锚点:参数寄存器直写(STORE_VAR)据此同步 arguments 存储值, + // 使嵌套调用期参数寄存器被覆盖时存储值仍为权威读源。 + if let Some(frame) = self.frames.last_mut() { + frame.arguments_obj = JsValue::from_js_object(obj_ptr); + } + } + } + // 索引属性:按实参下标写入 shape 槽,属性描述符为默认(可写/可枚举/可配置)。 // 下标走整数键,保证 `arguments[0]`(property_key_si(int 0))键等价命中。 for i in 0..count as usize { @@ -312,8 +401,6 @@ impl Vm { // 函数本身,可写、不可枚举、可配置)。 let callee_si = self.kernel_core.perm_interner().intern("callee").0; let callee = self.current_callee().unwrap_or(JsValue::undefined()); - let strict = self.current_strict(); - let simple = self.active_module().is_some_and(|m| m.has_simple_params); if strict || !simple { // SAFETY: 指针由绑定层在 session 构造期写入,session 存活期内有效。 let thrower_ptr = self.realm.session.borrow().builtin_world().throw_type_error.get(); diff --git a/crates/oxide_vm/src/lib.rs b/crates/oxide_vm/src/lib.rs index 71edca6b..66a8bb97 100644 --- a/crates/oxide_vm/src/lib.rs +++ b/crates/oxide_vm/src/lib.rs @@ -7,6 +7,7 @@ //! 核心入口为 [`vm::Vm`];`VmPool` 负责 VM 实例的复用;`session_gc` 提供 //! session 内存回收;`bindings` 把内置对象绑定到每个 session 的 global 上。 +mod arguments_gc; mod async_disposable; mod async_from_sync; mod async_func; diff --git a/crates/oxide_vm/src/native_box_dispatch.rs b/crates/oxide_vm/src/native_box_dispatch.rs index fd76b59b..5610ae7c 100644 --- a/crates/oxide_vm/src/native_box_dispatch.rs +++ b/crates/oxide_vm/src/native_box_dispatch.rs @@ -53,6 +53,8 @@ pub(crate) enum NativeBoxFamily { MessagePort, /// BroadcastChannel 通道对象(tag 34,mpsc 发送/接收对与通道名,无对象边)。 BroadcastChannel, + /// mapped arguments 对象(tag 20,同步状态盒存 `native_data`,无引用边)。 + Arguments, } /// 一个家族的边操作集(每家族函数引用,单一注册面)。 @@ -114,7 +116,7 @@ pub(crate) fn classify_by_tag(tag: u8) -> NativeBoxFamily { JsObject::OBJ_TYPE_DURATION => NativeBoxFamily::None, JsObject::OBJ_TYPE_ZONED_DATE_TIME => NativeBoxFamily::None, JsObject::OBJ_TYPE_PLAIN_DATE_TIME => NativeBoxFamily::None, - JsObject::OBJ_TYPE_ARGUMENTS => NativeBoxFamily::None, + JsObject::OBJ_TYPE_ARGUMENTS => NativeBoxFamily::Arguments, JsObject::OBJ_TYPE_SYMBOL_OBJ => NativeBoxFamily::None, JsObject::OBJ_TYPE_ERROR => NativeBoxFamily::None, JsObject::OBJ_TYPE_BOUND => NativeBoxFamily::None, @@ -221,6 +223,13 @@ pub(crate) fn ops_for(family: NativeBoxFamily) -> NativeBoxOps { string_edges: None, cell_edges: None, }, + // mapped arguments 同步状态盒:无引用边(位图与帧身份均为原始值), + // 三边函数全空,仅 size/drop 链消费。 + NativeBoxFamily::Arguments => NativeBoxOps { + object_edges: None, + string_edges: None, + cell_edges: None, + }, } } @@ -252,6 +261,7 @@ mod tests { JsObject::OBJ_TYPE_REGEXP | JsObject::OBJ_TYPE_REGEX_STUB => NativeBoxFamily::RegExp, JsObject::OBJ_TYPE_MESSAGE_PORT => NativeBoxFamily::MessagePort, JsObject::OBJ_TYPE_BROADCAST_CHANNEL => NativeBoxFamily::BroadcastChannel, + JsObject::OBJ_TYPE_ARGUMENTS => NativeBoxFamily::Arguments, _ => NativeBoxFamily::None, } } diff --git a/crates/oxide_vm/src/session_gc.rs b/crates/oxide_vm/src/session_gc.rs index 398196b5..d0b87d81 100644 --- a/crates/oxide_vm/src/session_gc.rs +++ b/crates/oxide_vm/src/session_gc.rs @@ -132,6 +132,7 @@ impl SessionGc { bytes += weak_map::weak_map_native_size(obj); bytes += message_channel::message_port_native_size(obj); bytes += broadcast_channel::broadcast_channel_native_size(obj); + bytes += crate::arguments_gc::arguments_native_size(obj); bytes } @@ -573,6 +574,7 @@ impl SessionGc { freed_bytes += weak_map::drop_weak_map_native(obj); freed_bytes += message_channel::drop_message_port_native(obj); freed_bytes += broadcast_channel::drop_broadcast_channel_native(obj); + freed_bytes += crate::arguments_gc::drop_arguments_native(obj); freed_bytes } diff --git a/crates/oxide_vm/src/session_gc/tests.rs b/crates/oxide_vm/src/session_gc/tests.rs index 82409c38..41556adf 100644 --- a/crates/oxide_vm/src/session_gc/tests.rs +++ b/crates/oxide_vm/src/session_gc/tests.rs @@ -89,6 +89,7 @@ fn gc_roots_contains_registers_frames_and_root_roots() { vm.frames.push(CallFrame { return_addr: 0, function_name: 0, + frame_id: 0, caller_reg_limit: 1, caller_active_reg_limit: 1, saved_reg_offset: 0, @@ -98,6 +99,7 @@ fn gc_roots_contains_registers_frames_and_root_roots() { saved_this: JsValue::from_js_object(this_session), saved_new_target: JsValue::from_js_object(child_session), callee: JsValue::from_js_object(child_session), + arguments_obj: JsValue::undefined(), construct_result_reg: None, strict: false, constructed_this: Some(JsValue::from_js_object(child_session)), diff --git a/crates/oxide_vm/src/suspended.rs b/crates/oxide_vm/src/suspended.rs index 3e36fd51..d2ffabf3 100644 --- a/crates/oxide_vm/src/suspended.rs +++ b/crates/oxide_vm/src/suspended.rs @@ -190,6 +190,7 @@ impl SuspendedFrame { f(frame.saved_this); f(frame.saved_new_target); f(frame.callee); + f(frame.arguments_obj); if let Some(ct) = frame.constructed_this { f(ct); } @@ -294,6 +295,7 @@ mod tests { CallFrame { return_addr: 7, function_name: 1, + frame_id: 0, caller_reg_limit: 2, caller_active_reg_limit: 2, saved_reg_offset: 3, @@ -303,6 +305,7 @@ mod tests { saved_this: tag, saved_new_target: tag, callee: tag, + arguments_obj: JsValue::undefined(), construct_result_reg: None, constructed_this: Some(tag), is_derived_constructor: false, diff --git a/crates/oxide_vm/src/vm/call.rs b/crates/oxide_vm/src/vm/call.rs index cd5293f4..49df80d4 100644 --- a/crates/oxide_vm/src/vm/call.rs +++ b/crates/oxide_vm/src/vm/call.rs @@ -332,9 +332,14 @@ impl Vm { // 压帧时固化 callee 的 upvalue 表(非闭包为 null 空切片):热路径免 // 每指令重查 callee 对象,指针跨 GC 恒有效(callee 是 GC 根)。 let up = obj.upvalues_slice() as *const [*mut Cell]; + // 帧身份单调递增:mapped arguments 状态盒按 (帧下标, 帧身份) 判定创建帧 + // 是否仍在栈上,帧弹出后身份不复用,映射随之失效。 + self.next_frame_id += 1; + let frame_id = self.next_frame_id; self.frames.push(CallFrame { return_addr: self.pc, function_name, + frame_id, caller_reg_limit, caller_active_reg_limit, saved_reg_offset, @@ -344,6 +349,7 @@ impl Vm { saved_this, saved_new_target, callee, + arguments_obj: JsValue::undefined(), construct_result_reg, constructed_this, is_derived_constructor: obj.is_derived_constructor(), diff --git a/crates/oxide_vm/src/vm/frames.rs b/crates/oxide_vm/src/vm/frames.rs index 288a63d9..e4085428 100644 --- a/crates/oxide_vm/src/vm/frames.rs +++ b/crates/oxide_vm/src/vm/frames.rs @@ -44,6 +44,9 @@ impl FrameArgs<'_> { pub struct CallFrame { pub return_addr: usize, pub function_name: u32, + /// 帧身份(VM 级单调计数器,压帧时分配):mapped arguments 状态盒按此判定 + /// 创建帧是否仍在栈上(帧弹出后映射失效)。 + pub frame_id: u64, /// 压帧保存的调用方寄存器窗口长度(= save_stack 中本帧段大小)。 /// 普通字节码调用按调用点存活上界截断;运行时发起(accessor/内联)为 /// 调用方 `active_reg_limit` 全量。 @@ -61,6 +64,9 @@ pub struct CallFrame { pub saved_this: JsValue, pub saved_new_target: JsValue, pub callee: JsValue, + /// 本帧的 mapped arguments 对象(参数寄存器与存储值双向同步的锚点); + /// 无 mapped arguments 时为 undefined。GC 根(帧遍历期标记)。 + pub arguments_obj: JsValue, pub construct_result_reg: Option, pub constructed_this: Option, pub is_derived_constructor: bool, diff --git a/crates/oxide_vm/src/vm/gc_hooks.rs b/crates/oxide_vm/src/vm/gc_hooks.rs index c61612fb..ed4e61b0 100644 --- a/crates/oxide_vm/src/vm/gc_hooks.rs +++ b/crates/oxide_vm/src/vm/gc_hooks.rs @@ -75,6 +75,7 @@ impl Vm { f(RootGroup::Frames, frame.saved_this); f(RootGroup::Frames, frame.saved_new_target); f(RootGroup::Frames, frame.callee); + f(RootGroup::Frames, frame.arguments_obj); f(RootGroup::Frames, frame.constructed_this.unwrap_or(JsValue::undefined())); } for &v in &self.save_stack { diff --git a/crates/oxide_vm/src/vm/mod.rs b/crates/oxide_vm/src/vm/mod.rs index 113ce7c3..2ca9b53f 100644 --- a/crates/oxide_vm/src/vm/mod.rs +++ b/crates/oxide_vm/src/vm/mod.rs @@ -201,6 +201,9 @@ pub struct Vm { /// 用胖 `*const`:常量 Vec 归表代际所有且 OnceLock 只填一次(堆分配地址稳定)。 pub(crate) active_immutables: *const [JsValue], pub(crate) frames: SmallVec<[CallFrame; 16]>, + /// 帧身份单调计数器:压帧时分配 `CallFrame.frame_id`(mapped arguments 状态盒 + /// 按此判定创建帧是否仍在栈上)。 + pub(crate) next_frame_id: u64, pub(crate) kernel_core: Arc, /// 动态编译服务句柄:`Function` 构造器 / eval / `$262.evalScript` 经它编译 /// 源码(`dynamic.rs` 三方法)。缺省为 no-op stub(返 `Err`),生产 entry diff --git a/crates/oxide_vm/src/vm/vmhost.rs b/crates/oxide_vm/src/vm/vmhost.rs index f4074560..c93c0886 100644 --- a/crates/oxide_vm/src/vm/vmhost.rs +++ b/crates/oxide_vm/src/vm/vmhost.rs @@ -215,6 +215,9 @@ impl oxide_runtime_api::VmHost for Vm { fn get_own_property_slot(&self, obj: &JsObject, prop_name_si: u32) -> Option { self.get_own_property_slot(obj, prop_name_si) } + fn arguments_mapping_alive(&self, state: &oxide_types::arguments_map::ArgumentsMapState) -> bool { + self.arguments_mapping_alive(state) + } fn ordinary_get(&mut self, obj: &JsObject, prop_name_si: u32, receiver: JsValue) -> Result { self.ordinary_get(obj, prop_name_si, receiver) } diff --git a/crates/oxide_vm/src/vm_props.rs b/crates/oxide_vm/src/vm_props.rs index bb084ea3..76a70e09 100644 --- a/crates/oxide_vm/src/vm_props.rs +++ b/crates/oxide_vm/src/vm_props.rs @@ -4,10 +4,22 @@ use oxide_kernel::prop_forge::PropTemplate; use oxide_kernel::shape_forge::EMPTY_SHAPE_ID; use oxide_runtime_api as coercion; use oxide_runtime_api::VmHost; +use oxide_types::arguments_map::ArgumentsMapState; use oxide_types::object::{JsObject, PropAttributes, PropMetaEntry}; use oxide_types::value::JsValue; impl Vm { + /// 判定 mapped arguments 映射是否存活:创建帧仍在帧栈上且帧身份匹配。 + /// + /// # 边界与前提 + /// - `frame_depth` 为创建帧在帧栈中的下标;帧弹出后 `frames.len() <= frame_depth`, + /// 映射失效(参数寄存器已被外层帧复用)。 + /// - 帧身份(`frame_id`)单调递增不复用,防帧下标复用误判。 + pub(crate) fn arguments_mapping_alive(&self, state: &ArgumentsMapState) -> bool { + let depth = state.frame_depth as usize; + self.frames.len() > depth && self.frames[depth].frame_id == state.frame_id + } + /// 属性读入口:解析 `obj[prop_name_si]`,依次尝试数组 length 虚拟属性、 /// 数组元素区、TypedArray 整数索引、命名属性槽,最后沿原型链查找。 /// @@ -378,6 +390,39 @@ impl Vm { } return Ok(()); } + // mapped arguments exotic [[Set]]:整数索引且映射存活时同步存储值(读路径 + // 权威源)并写参数寄存器;accessor 属性落穿普通 setter 路径。参数寄存器 + // 仅在创建帧为顶帧时写——嵌套调用期寄存器被被调方占用,写之串值,参数值 + // 在 save_stack。 + if obj.is_arguments_obj() { + let state_ptr = obj.native_data() as *const ArgumentsMapState; + if !state_ptr.is_null() { + // SAFETY: native_data 由创建期 Box::into_raw 分配,生命周期与对象一致。 + let state = unsafe { &*state_ptr }; + if self.arguments_mapping_alive(state) { + if let Some(index) = self.array_index_from_property_key(prop_name_si) { + if (index as u16) < state.param_count && state.is_mapped(index as u16) { + let pos = self.get_own_property_slot(obj, prop_name_si); + let meta = pos.and_then(|p| obj.prop_meta_at(p)); + let is_accessor = meta.is_some_and(|m| m.is_accessor); + if !is_accessor { + let writable = meta.map(|m| m.attributes.writable()).unwrap_or(true); + if !writable { + return self + .write_protection_failure(builtin, "cannot assign to read-only property"); + } + self.set_or_create_prop_value(obj, prop_name_si, val); + if self.frames.len() == state.frame_depth as usize + 1 { + let reg = state.param_base as usize + index as usize; + self.regs[reg] = val; + } + return Ok(()); + } + } + } + } + } + } // 统一数值键门(exotic [[Set]]):界内规范键写底层 buffer(自臂界内 // 写 / 越界静默;他臂先按自身判界再按 receiver 分类);数字无效键强转 // (可抛、kind 保真)后丢弃、receiver 不 consult(detach 同臂:live 长 @@ -2029,7 +2074,10 @@ mod tests { let ns = unsafe { &*ns_ptr }; assert!(vm.ordinary_get(ns, x_si, ns_val).is_err(), "首次读应抛错"); let ns = unsafe { &*ns_ptr }; - let cached_err = oxide_builtins::module::deferred_state(ns).unwrap().error.expect("失败应缓存错误"); + let cached_err = oxide_builtins::module::deferred_state(ns) + .unwrap() + .error + .expect("失败应缓存错误"); // 再读:重抛同一错误对象(sameValue 身份)。 let ns = unsafe { &*ns_ptr }; @@ -2055,7 +2103,10 @@ mod tests { // 求值未触发:[[Evaluated]] 仍为 false。 let ns = unsafe { &*ns_ptr }; - assert!(!oxide_builtins::module::deferred_state(ns).unwrap().evaluated, "symbol-like 键不应触发求值"); + assert!( + !oxide_builtins::module::deferred_state(ns).unwrap().evaluated, + "symbol-like 键不应触发求值" + ); } #[test] diff --git a/crates/oxide_vm/src/vm_support.rs b/crates/oxide_vm/src/vm_support.rs index 00fba9c4..fdf1ca49 100644 --- a/crates/oxide_vm/src/vm_support.rs +++ b/crates/oxide_vm/src/vm_support.rs @@ -67,6 +67,7 @@ impl Vm { bytecode: Arc::default(), active_immutables: std::ptr::slice_from_raw_parts(std::ptr::null(), 0), frames: smallvec::SmallVec::new(), + next_frame_id: 0, kernel_core: core, // 缺省 no-op 编译服务:动态编译返 Err,生产 entry points 与 // 动态编译测试经 set_compiler_service 注入真实实现。 @@ -232,6 +233,7 @@ impl Vm { bytecode: Arc::default(), active_immutables: std::ptr::slice_from_raw_parts(std::ptr::null(), 0), frames: smallvec::SmallVec::new(), + next_frame_id: 0, kernel_core: core, // 缺省 no-op 编译服务:动态编译返 Err,生产 entry points 与 // 动态编译测试经 set_compiler_service 注入真实实现。 diff --git a/crates/oxide_vm/src/vm_support/tests.rs b/crates/oxide_vm/src/vm_support/tests.rs index e7faf437..ea7c69f5 100644 --- a/crates/oxide_vm/src/vm_support/tests.rs +++ b/crates/oxide_vm/src/vm_support/tests.rs @@ -797,6 +797,7 @@ fn reset_clears_runtime_state_like_rerun() { vm.frames.push(crate::vm::CallFrame { return_addr: 1, function_name: 0, + frame_id: 0, caller_reg_limit: 2, caller_active_reg_limit: 2, saved_reg_offset: 0, @@ -806,6 +807,7 @@ fn reset_clears_runtime_state_like_rerun() { saved_this: JsValue::undefined(), saved_new_target: JsValue::undefined(), callee: JsValue::undefined(), + arguments_obj: JsValue::undefined(), construct_result_reg: None, strict: false, constructed_this: None,