feat: mapped arguments defineProperty 后参数与 arguments 再同步

This commit is contained in:
2026-10-09 23:27:18 +00:00
parent 6ba2043599
commit 2b18c4d264
21 changed files with 346 additions and 25 deletions
Generated
+19 -19
View File
@@ -1160,7 +1160,7 @@ dependencies = [
[[package]] [[package]]
name = "oxide_builtins" name = "oxide_builtins"
version = "0.13.21" version = "0.13.22"
dependencies = [ dependencies = [
"chrono", "chrono",
"fast-float", "fast-float",
@@ -1185,7 +1185,7 @@ dependencies = [
[[package]] [[package]]
name = "oxide_bytecode" name = "oxide_bytecode"
version = "0.13.21" version = "0.13.22"
dependencies = [ dependencies = [
"num-bigint", "num-bigint",
"oxide_log", "oxide_log",
@@ -1193,7 +1193,7 @@ dependencies = [
[[package]] [[package]]
name = "oxide_cfg" name = "oxide_cfg"
version = "0.13.21" version = "0.13.22"
dependencies = [ dependencies = [
"oxide_bytecode", "oxide_bytecode",
"oxide_emit", "oxide_emit",
@@ -1204,7 +1204,7 @@ dependencies = [
[[package]] [[package]]
name = "oxide_cli" name = "oxide_cli"
version = "0.13.21" version = "0.13.22"
dependencies = [ dependencies = [
"ansi_term", "ansi_term",
"bumpalo", "bumpalo",
@@ -1226,7 +1226,7 @@ dependencies = [
[[package]] [[package]]
name = "oxide_code_cache" name = "oxide_code_cache"
version = "0.13.21" version = "0.13.22"
dependencies = [ dependencies = [
"lru", "lru",
"oxide_bytecode", "oxide_bytecode",
@@ -1235,7 +1235,7 @@ dependencies = [
[[package]] [[package]]
name = "oxide_compiler" name = "oxide_compiler"
version = "0.13.21" version = "0.13.22"
dependencies = [ dependencies = [
"oxide_bytecode", "oxide_bytecode",
"oxide_cfg", "oxide_cfg",
@@ -1255,7 +1255,7 @@ dependencies = [
[[package]] [[package]]
name = "oxide_dce" name = "oxide_dce"
version = "0.13.21" version = "0.13.22"
dependencies = [ dependencies = [
"oxide_bytecode", "oxide_bytecode",
"oxide_cfg", "oxide_cfg",
@@ -1269,7 +1269,7 @@ dependencies = [
[[package]] [[package]]
name = "oxide_emit" name = "oxide_emit"
version = "0.13.21" version = "0.13.22"
dependencies = [ dependencies = [
"num-bigint", "num-bigint",
"oxc_allocator", "oxc_allocator",
@@ -1284,7 +1284,7 @@ dependencies = [
[[package]] [[package]]
name = "oxide_engine" name = "oxide_engine"
version = "0.13.21" version = "0.13.22"
dependencies = [ dependencies = [
"oxide_bytecode", "oxide_bytecode",
"oxide_compiler", "oxide_compiler",
@@ -1296,7 +1296,7 @@ dependencies = [
[[package]] [[package]]
name = "oxide_ir" name = "oxide_ir"
version = "0.13.21" version = "0.13.22"
dependencies = [ dependencies = [
"oxide_bytecode", "oxide_bytecode",
"oxide_log", "oxide_log",
@@ -1305,7 +1305,7 @@ dependencies = [
[[package]] [[package]]
name = "oxide_kernel" name = "oxide_kernel"
version = "0.13.21" version = "0.13.22"
dependencies = [ dependencies = [
"bumpalo", "bumpalo",
"dashmap", "dashmap",
@@ -1319,7 +1319,7 @@ dependencies = [
[[package]] [[package]]
name = "oxide_liveness" name = "oxide_liveness"
version = "0.13.21" version = "0.13.22"
dependencies = [ dependencies = [
"oxide_bytecode", "oxide_bytecode",
"oxide_cfg", "oxide_cfg",
@@ -1331,7 +1331,7 @@ dependencies = [
[[package]] [[package]]
name = "oxide_log" name = "oxide_log"
version = "0.13.21" version = "0.13.22"
dependencies = [ dependencies = [
"tracing", "tracing",
"tracing-appender", "tracing-appender",
@@ -1340,7 +1340,7 @@ dependencies = [
[[package]] [[package]]
name = "oxide_parser" name = "oxide_parser"
version = "0.13.21" version = "0.13.22"
dependencies = [ dependencies = [
"oxc_allocator", "oxc_allocator",
"oxc_ast", "oxc_ast",
@@ -1355,7 +1355,7 @@ dependencies = [
[[package]] [[package]]
name = "oxide_regalloc" name = "oxide_regalloc"
version = "0.13.21" version = "0.13.22"
dependencies = [ dependencies = [
"oxide_bytecode", "oxide_bytecode",
"oxide_cfg", "oxide_cfg",
@@ -1371,7 +1371,7 @@ dependencies = [
[[package]] [[package]]
name = "oxide_runtime_api" name = "oxide_runtime_api"
version = "0.13.21" version = "0.13.22"
dependencies = [ dependencies = [
"num-bigint", "num-bigint",
"num-traits", "num-traits",
@@ -1384,7 +1384,7 @@ dependencies = [
[[package]] [[package]]
name = "oxide_test262" name = "oxide_test262"
version = "0.13.21" version = "0.13.22"
dependencies = [ dependencies = [
"oxide_bytecode", "oxide_bytecode",
"oxide_compiler", "oxide_compiler",
@@ -1402,7 +1402,7 @@ dependencies = [
[[package]] [[package]]
name = "oxide_types" name = "oxide_types"
version = "0.13.21" version = "0.13.22"
dependencies = [ dependencies = [
"bumpalo", "bumpalo",
"dashmap", "dashmap",
@@ -1415,7 +1415,7 @@ dependencies = [
[[package]] [[package]]
name = "oxide_vm" name = "oxide_vm"
version = "0.13.21" version = "0.13.22"
dependencies = [ dependencies = [
"bumpalo", "bumpalo",
"chrono", "chrono",
+1 -1
View File
@@ -23,7 +23,7 @@ members = [
] ]
[workspace.package] [workspace.package]
version = "0.13.21" version = "0.13.22"
edition = "2021" edition = "2021"
license = "MIT" license = "MIT"
rust-version = "1.80" rust-version = "1.80"
+60
View File
@@ -1,5 +1,6 @@
use oxide_kernel::shape_forge::{ShapeForge, EMPTY_SHAPE_ID}; use oxide_kernel::shape_forge::{ShapeForge, EMPTY_SHAPE_ID};
use oxide_kernel::string_forge::PermInterner; use oxide_kernel::string_forge::PermInterner;
use oxide_types::arguments_map::ArgumentsMapState;
use oxide_types::object::{JsObject, PropAttributes, PropMetaEntry}; use oxide_types::object::{JsObject, PropAttributes, PropMetaEntry};
use oxide_types::private_key::{ 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, 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<H: VmHost>(
} }
} }
// 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 不变)。 // 数组下标元素在元素区,不参与 shape 链,单独删除(保持 length 不变)。
if obj.is_array() { if obj.is_array() {
if let Some(index) = array_index_of(vm, key_si) { if let Some(index) = array_index_of(vm, key_si) {
@@ -955,6 +983,38 @@ pub(crate) fn define_from_descriptor<H: VmHost>(
} }
} }
// 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 }; let obj = unsafe { &mut *obj_ptr };
if has_accessor { if has_accessor {
vm.define_accessor_property(obj, key_si, get, set, PropAttributes::new(false, enumerable, configurable)) vm.define_accessor_property(obj, key_si, get, set, PropAttributes::new(false, enumerable, configurable))
+6
View File
@@ -253,6 +253,12 @@ pub trait VmHost {
/// 守卫抛 TypeError / 失败缓存原值重抛),触发失败以 `Err` 返回。 /// 守卫抛 TypeError / 失败缓存原值重抛),触发失败以 `Err` 返回。
fn has_property(&mut self, obj: &JsObject, prop_name_si: u32) -> Result<bool, String>; fn has_property(&mut self, obj: &JsObject, prop_name_si: u32) -> Result<bool, String>;
fn get_own_property_slot(&self, obj: &JsObject, prop_name_si: u32) -> Option<u32>; fn get_own_property_slot(&self, obj: &JsObject, prop_name_si: u32) -> Option<u32>;
/// 判定 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<JsValue, String>; fn ordinary_get(&mut self, obj: &JsObject, prop_name_si: u32, receiver: JsValue) -> Result<JsValue, String>;
+39
View File
@@ -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<u64>,
/// 创建帧在帧栈中的下标(创建期 `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);
}
}
}
+2
View File
@@ -1,5 +1,7 @@
#![doc = "OxideJS 共享核心类型:ECMAScript 值、对象模型、形状存储、内存抽象与运行时错误类型。"] #![doc = "OxideJS 共享核心类型:ECMAScript 值、对象模型、形状存储、内存抽象与运行时错误类型。"]
/// mapped arguments 对象同步状态盒(参数寄存器与 arguments 存储值双向同步)。
pub mod arguments_map;
/// 运行时错误类型([`JsError`] / [`JsErrorKind`])。 /// 运行时错误类型([`JsError`] / [`JsErrorKind`])。
pub mod error; pub mod error;
/// 内存抽象:持久指针、arena 分配(epoch)与持久堆。 /// 内存抽象:持久指针、arena 分配(epoch)与持久堆。
+35
View File
@@ -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::<ArgumentsMapState>() as u64 + (*ptr).mapped_mask.capacity() as u64 * size_of::<u64>() 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
}
+89 -2
View File
@@ -2,6 +2,7 @@ use crate::vm::Vm;
use crate::{vm_error, vm_trace}; use crate::{vm_error, vm_trace};
use oxide_bytecode::opcode; use oxide_bytecode::opcode;
use oxide_kernel::shape_forge::EMPTY_SHAPE_ID; use oxide_kernel::shape_forge::EMPTY_SHAPE_ID;
use oxide_types::arguments_map::ArgumentsMapState;
use oxide_types::object::{JsObject, PropAttributes}; use oxide_types::object::{JsObject, PropAttributes};
use oxide_types::private_key::{encode_symbol_key, make_int_key}; use oxide_types::private_key::{encode_symbol_key, make_int_key};
use oxide_types::value::{JsType, JsValue}; use oxide_types::value::{JsType, JsValue};
@@ -153,9 +154,63 @@ impl Vm {
} }
} }
self.regs[rd] = self.regs[a]; self.regs[rd] = self.regs[a];
// mapped arguments 参数直写同步:目标寄存器是创建帧的存活映射参数时,
// 把新值回写 arguments 存储值(读路径权威源),嵌套调用覆盖寄存器后
// 存储值仍正确。
self.sync_mapped_arguments_param(rd);
Ok(false) 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]`。 /// SPILL:`regs[rd]` → `spill_stack[帧基址 + slot]`。
/// ///
/// ext 字为 slot(u16);帧基址 = 当前 CallFrame.spill_offset(顶层模块无帧为 0)。 /// ext 字为 slot(u16);帧基址 = 当前 CallFrame.spill_offset(顶层模块无帧为 0)。
@@ -287,6 +342,40 @@ impl Vm {
let obj = unsafe { &mut *obj_ptr }; let obj = unsafe { &mut *obj_ptr };
obj.type_tag = JsObject::OBJ_TYPE_ARGUMENTS; 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 槽,属性描述符为默认(可写/可枚举/可配置)。 // 索引属性:按实参下标写入 shape 槽,属性描述符为默认(可写/可枚举/可配置)。
// 下标走整数键,保证 `arguments[0]`(property_key_si(int 0))键等价命中。 // 下标走整数键,保证 `arguments[0]`(property_key_si(int 0))键等价命中。
for i in 0..count as usize { 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_si = self.kernel_core.perm_interner().intern("callee").0;
let callee = self.current_callee().unwrap_or(JsValue::undefined()); 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 { if strict || !simple {
// SAFETY: 指针由绑定层在 session 构造期写入,session 存活期内有效。 // SAFETY: 指针由绑定层在 session 构造期写入,session 存活期内有效。
let thrower_ptr = self.realm.session.borrow().builtin_world().throw_type_error.get(); let thrower_ptr = self.realm.session.borrow().builtin_world().throw_type_error.get();
+1
View File
@@ -7,6 +7,7 @@
//! 核心入口为 [`vm::Vm`];`VmPool` 负责 VM 实例的复用;`session_gc` 提供 //! 核心入口为 [`vm::Vm`];`VmPool` 负责 VM 实例的复用;`session_gc` 提供
//! session 内存回收;`bindings` 把内置对象绑定到每个 session 的 global 上。 //! session 内存回收;`bindings` 把内置对象绑定到每个 session 的 global 上。
mod arguments_gc;
mod async_disposable; mod async_disposable;
mod async_from_sync; mod async_from_sync;
mod async_func; mod async_func;
+11 -1
View File
@@ -53,6 +53,8 @@ pub(crate) enum NativeBoxFamily {
MessagePort, MessagePort,
/// BroadcastChannel 通道对象(tag 34,mpsc 发送/接收对与通道名,无对象边)。 /// BroadcastChannel 通道对象(tag 34,mpsc 发送/接收对与通道名,无对象边)。
BroadcastChannel, 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_DURATION => NativeBoxFamily::None,
JsObject::OBJ_TYPE_ZONED_DATE_TIME => NativeBoxFamily::None, JsObject::OBJ_TYPE_ZONED_DATE_TIME => NativeBoxFamily::None,
JsObject::OBJ_TYPE_PLAIN_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_SYMBOL_OBJ => NativeBoxFamily::None,
JsObject::OBJ_TYPE_ERROR => NativeBoxFamily::None, JsObject::OBJ_TYPE_ERROR => NativeBoxFamily::None,
JsObject::OBJ_TYPE_BOUND => NativeBoxFamily::None, JsObject::OBJ_TYPE_BOUND => NativeBoxFamily::None,
@@ -221,6 +223,13 @@ pub(crate) fn ops_for(family: NativeBoxFamily) -> NativeBoxOps {
string_edges: None, string_edges: None,
cell_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_REGEXP | JsObject::OBJ_TYPE_REGEX_STUB => NativeBoxFamily::RegExp,
JsObject::OBJ_TYPE_MESSAGE_PORT => NativeBoxFamily::MessagePort, JsObject::OBJ_TYPE_MESSAGE_PORT => NativeBoxFamily::MessagePort,
JsObject::OBJ_TYPE_BROADCAST_CHANNEL => NativeBoxFamily::BroadcastChannel, JsObject::OBJ_TYPE_BROADCAST_CHANNEL => NativeBoxFamily::BroadcastChannel,
JsObject::OBJ_TYPE_ARGUMENTS => NativeBoxFamily::Arguments,
_ => NativeBoxFamily::None, _ => NativeBoxFamily::None,
} }
} }
+2
View File
@@ -132,6 +132,7 @@ impl SessionGc {
bytes += weak_map::weak_map_native_size(obj); bytes += weak_map::weak_map_native_size(obj);
bytes += message_channel::message_port_native_size(obj); bytes += message_channel::message_port_native_size(obj);
bytes += broadcast_channel::broadcast_channel_native_size(obj); bytes += broadcast_channel::broadcast_channel_native_size(obj);
bytes += crate::arguments_gc::arguments_native_size(obj);
bytes bytes
} }
@@ -573,6 +574,7 @@ impl SessionGc {
freed_bytes += weak_map::drop_weak_map_native(obj); freed_bytes += weak_map::drop_weak_map_native(obj);
freed_bytes += message_channel::drop_message_port_native(obj); freed_bytes += message_channel::drop_message_port_native(obj);
freed_bytes += broadcast_channel::drop_broadcast_channel_native(obj); freed_bytes += broadcast_channel::drop_broadcast_channel_native(obj);
freed_bytes += crate::arguments_gc::drop_arguments_native(obj);
freed_bytes freed_bytes
} }
+2
View File
@@ -89,6 +89,7 @@ fn gc_roots_contains_registers_frames_and_root_roots() {
vm.frames.push(CallFrame { vm.frames.push(CallFrame {
return_addr: 0, return_addr: 0,
function_name: 0, function_name: 0,
frame_id: 0,
caller_reg_limit: 1, caller_reg_limit: 1,
caller_active_reg_limit: 1, caller_active_reg_limit: 1,
saved_reg_offset: 0, 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_this: JsValue::from_js_object(this_session),
saved_new_target: JsValue::from_js_object(child_session), saved_new_target: JsValue::from_js_object(child_session),
callee: JsValue::from_js_object(child_session), callee: JsValue::from_js_object(child_session),
arguments_obj: JsValue::undefined(),
construct_result_reg: None, construct_result_reg: None,
strict: false, strict: false,
constructed_this: Some(JsValue::from_js_object(child_session)), constructed_this: Some(JsValue::from_js_object(child_session)),
+3
View File
@@ -190,6 +190,7 @@ impl SuspendedFrame {
f(frame.saved_this); f(frame.saved_this);
f(frame.saved_new_target); f(frame.saved_new_target);
f(frame.callee); f(frame.callee);
f(frame.arguments_obj);
if let Some(ct) = frame.constructed_this { if let Some(ct) = frame.constructed_this {
f(ct); f(ct);
} }
@@ -294,6 +295,7 @@ mod tests {
CallFrame { CallFrame {
return_addr: 7, return_addr: 7,
function_name: 1, function_name: 1,
frame_id: 0,
caller_reg_limit: 2, caller_reg_limit: 2,
caller_active_reg_limit: 2, caller_active_reg_limit: 2,
saved_reg_offset: 3, saved_reg_offset: 3,
@@ -303,6 +305,7 @@ mod tests {
saved_this: tag, saved_this: tag,
saved_new_target: tag, saved_new_target: tag,
callee: tag, callee: tag,
arguments_obj: JsValue::undefined(),
construct_result_reg: None, construct_result_reg: None,
constructed_this: Some(tag), constructed_this: Some(tag),
is_derived_constructor: false, is_derived_constructor: false,
+6
View File
@@ -332,9 +332,14 @@ impl Vm {
// 压帧时固化 callee 的 upvalue 表(非闭包为 null 空切片):热路径免 // 压帧时固化 callee 的 upvalue 表(非闭包为 null 空切片):热路径免
// 每指令重查 callee 对象,指针跨 GC 恒有效(callee 是 GC 根)。 // 每指令重查 callee 对象,指针跨 GC 恒有效(callee 是 GC 根)。
let up = obj.upvalues_slice() as *const [*mut Cell]; 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 { self.frames.push(CallFrame {
return_addr: self.pc, return_addr: self.pc,
function_name, function_name,
frame_id,
caller_reg_limit, caller_reg_limit,
caller_active_reg_limit, caller_active_reg_limit,
saved_reg_offset, saved_reg_offset,
@@ -344,6 +349,7 @@ impl Vm {
saved_this, saved_this,
saved_new_target, saved_new_target,
callee, callee,
arguments_obj: JsValue::undefined(),
construct_result_reg, construct_result_reg,
constructed_this, constructed_this,
is_derived_constructor: obj.is_derived_constructor(), is_derived_constructor: obj.is_derived_constructor(),
+6
View File
@@ -44,6 +44,9 @@ impl FrameArgs<'_> {
pub struct CallFrame { pub struct CallFrame {
pub return_addr: usize, pub return_addr: usize,
pub function_name: u32, pub function_name: u32,
/// 帧身份(VM 级单调计数器,压帧时分配):mapped arguments 状态盒按此判定
/// 创建帧是否仍在栈上(帧弹出后映射失效)。
pub frame_id: u64,
/// 压帧保存的调用方寄存器窗口长度(= save_stack 中本帧段大小)。 /// 压帧保存的调用方寄存器窗口长度(= save_stack 中本帧段大小)。
/// 普通字节码调用按调用点存活上界截断;运行时发起(accessor/内联)为 /// 普通字节码调用按调用点存活上界截断;运行时发起(accessor/内联)为
/// 调用方 `active_reg_limit` 全量。 /// 调用方 `active_reg_limit` 全量。
@@ -61,6 +64,9 @@ pub struct CallFrame {
pub saved_this: JsValue, pub saved_this: JsValue,
pub saved_new_target: JsValue, pub saved_new_target: JsValue,
pub callee: JsValue, pub callee: JsValue,
/// 本帧的 mapped arguments 对象(参数寄存器与存储值双向同步的锚点);
/// 无 mapped arguments 时为 undefined。GC 根(帧遍历期标记)。
pub arguments_obj: JsValue,
pub construct_result_reg: Option<u8>, pub construct_result_reg: Option<u8>,
pub constructed_this: Option<JsValue>, pub constructed_this: Option<JsValue>,
pub is_derived_constructor: bool, pub is_derived_constructor: bool,
+1
View File
@@ -75,6 +75,7 @@ impl Vm {
f(RootGroup::Frames, frame.saved_this); f(RootGroup::Frames, frame.saved_this);
f(RootGroup::Frames, frame.saved_new_target); f(RootGroup::Frames, frame.saved_new_target);
f(RootGroup::Frames, frame.callee); f(RootGroup::Frames, frame.callee);
f(RootGroup::Frames, frame.arguments_obj);
f(RootGroup::Frames, frame.constructed_this.unwrap_or(JsValue::undefined())); f(RootGroup::Frames, frame.constructed_this.unwrap_or(JsValue::undefined()));
} }
for &v in &self.save_stack { for &v in &self.save_stack {
+3
View File
@@ -201,6 +201,9 @@ pub struct Vm {
/// 用胖 `*const`:常量 Vec 归表代际所有且 OnceLock 只填一次(堆分配地址稳定)。 /// 用胖 `*const`:常量 Vec 归表代际所有且 OnceLock 只填一次(堆分配地址稳定)。
pub(crate) active_immutables: *const [JsValue], pub(crate) active_immutables: *const [JsValue],
pub(crate) frames: SmallVec<[CallFrame; 16]>, pub(crate) frames: SmallVec<[CallFrame; 16]>,
/// 帧身份单调计数器:压帧时分配 `CallFrame.frame_id`(mapped arguments 状态盒
/// 按此判定创建帧是否仍在栈上)。
pub(crate) next_frame_id: u64,
pub(crate) kernel_core: Arc<KernelCore>, pub(crate) kernel_core: Arc<KernelCore>,
/// 动态编译服务句柄:`Function` 构造器 / eval / `$262.evalScript` 经它编译 /// 动态编译服务句柄:`Function` 构造器 / eval / `$262.evalScript` 经它编译
/// 源码(`dynamic.rs` 三方法)。缺省为 no-op stub(返 `Err`),生产 entry /// 源码(`dynamic.rs` 三方法)。缺省为 no-op stub(返 `Err`),生产 entry
+3
View File
@@ -215,6 +215,9 @@ impl oxide_runtime_api::VmHost for Vm {
fn get_own_property_slot(&self, obj: &JsObject, prop_name_si: u32) -> Option<u32> { fn get_own_property_slot(&self, obj: &JsObject, prop_name_si: u32) -> Option<u32> {
self.get_own_property_slot(obj, prop_name_si) 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<JsValue, String> { fn ordinary_get(&mut self, obj: &JsObject, prop_name_si: u32, receiver: JsValue) -> Result<JsValue, String> {
self.ordinary_get(obj, prop_name_si, receiver) self.ordinary_get(obj, prop_name_si, receiver)
} }
+53 -2
View File
@@ -4,10 +4,22 @@ use oxide_kernel::prop_forge::PropTemplate;
use oxide_kernel::shape_forge::EMPTY_SHAPE_ID; use oxide_kernel::shape_forge::EMPTY_SHAPE_ID;
use oxide_runtime_api as coercion; use oxide_runtime_api as coercion;
use oxide_runtime_api::VmHost; use oxide_runtime_api::VmHost;
use oxide_types::arguments_map::ArgumentsMapState;
use oxide_types::object::{JsObject, PropAttributes, PropMetaEntry}; use oxide_types::object::{JsObject, PropAttributes, PropMetaEntry};
use oxide_types::value::JsValue; use oxide_types::value::JsValue;
impl Vm { 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 虚拟属性、 /// 属性读入口:解析 `obj[prop_name_si]`,依次尝试数组 length 虚拟属性、
/// 数组元素区、TypedArray 整数索引、命名属性槽,最后沿原型链查找。 /// 数组元素区、TypedArray 整数索引、命名属性槽,最后沿原型链查找。
/// ///
@@ -378,6 +390,39 @@ impl Vm {
} }
return Ok(()); 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(自臂界内 // 统一数值键门(exotic [[Set]]):界内规范键写底层 buffer(自臂界内
// 写 / 越界静默;他臂先按自身判界再按 receiver 分类);数字无效键强转 // 写 / 越界静默;他臂先按自身判界再按 receiver 分类);数字无效键强转
// (可抛、kind 保真)后丢弃、receiver 不 consult(detach 同臂:live 长 // (可抛、kind 保真)后丢弃、receiver 不 consult(detach 同臂:live 长
@@ -2029,7 +2074,10 @@ mod tests {
let ns = unsafe { &*ns_ptr }; let ns = unsafe { &*ns_ptr };
assert!(vm.ordinary_get(ns, x_si, ns_val).is_err(), "首次读应抛错"); assert!(vm.ordinary_get(ns, x_si, ns_val).is_err(), "首次读应抛错");
let ns = unsafe { &*ns_ptr }; 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 身份)。 // 再读:重抛同一错误对象(sameValue 身份)。
let ns = unsafe { &*ns_ptr }; let ns = unsafe { &*ns_ptr };
@@ -2055,7 +2103,10 @@ mod tests {
// 求值未触发:[[Evaluated]] 仍为 false。 // 求值未触发:[[Evaluated]] 仍为 false。
let ns = unsafe { &*ns_ptr }; 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] #[test]
+2
View File
@@ -67,6 +67,7 @@ impl Vm {
bytecode: Arc::default(), bytecode: Arc::default(),
active_immutables: std::ptr::slice_from_raw_parts(std::ptr::null(), 0), active_immutables: std::ptr::slice_from_raw_parts(std::ptr::null(), 0),
frames: smallvec::SmallVec::new(), frames: smallvec::SmallVec::new(),
next_frame_id: 0,
kernel_core: core, kernel_core: core,
// 缺省 no-op 编译服务:动态编译返 Err,生产 entry points 与 // 缺省 no-op 编译服务:动态编译返 Err,生产 entry points 与
// 动态编译测试经 set_compiler_service 注入真实实现。 // 动态编译测试经 set_compiler_service 注入真实实现。
@@ -232,6 +233,7 @@ impl Vm {
bytecode: Arc::default(), bytecode: Arc::default(),
active_immutables: std::ptr::slice_from_raw_parts(std::ptr::null(), 0), active_immutables: std::ptr::slice_from_raw_parts(std::ptr::null(), 0),
frames: smallvec::SmallVec::new(), frames: smallvec::SmallVec::new(),
next_frame_id: 0,
kernel_core: core, kernel_core: core,
// 缺省 no-op 编译服务:动态编译返 Err,生产 entry points 与 // 缺省 no-op 编译服务:动态编译返 Err,生产 entry points 与
// 动态编译测试经 set_compiler_service 注入真实实现。 // 动态编译测试经 set_compiler_service 注入真实实现。
+2
View File
@@ -797,6 +797,7 @@ fn reset_clears_runtime_state_like_rerun() {
vm.frames.push(crate::vm::CallFrame { vm.frames.push(crate::vm::CallFrame {
return_addr: 1, return_addr: 1,
function_name: 0, function_name: 0,
frame_id: 0,
caller_reg_limit: 2, caller_reg_limit: 2,
caller_active_reg_limit: 2, caller_active_reg_limit: 2,
saved_reg_offset: 0, saved_reg_offset: 0,
@@ -806,6 +807,7 @@ fn reset_clears_runtime_state_like_rerun() {
saved_this: JsValue::undefined(), saved_this: JsValue::undefined(),
saved_new_target: JsValue::undefined(), saved_new_target: JsValue::undefined(),
callee: JsValue::undefined(), callee: JsValue::undefined(),
arguments_obj: JsValue::undefined(),
construct_result_reg: None, construct_result_reg: None,
strict: false, strict: false,
constructed_this: None, constructed_this: None,