From 2c62830a1de177b9e07a16777a21da75e370a9db Mon Sep 17 00:00:00 2001 From: pjh456 <147148383@qq.com> Date: Thu, 13 Aug 2026 17:47:55 +0800 Subject: [PATCH] =?UTF-8?q?perf:=20=E6=95=B0=E7=BB=84=E6=95=B4=E6=95=B0?= =?UTF-8?q?=E9=94=AE=E8=B5=B0=E7=8B=AC=E7=AB=8B=20si=20=E5=8C=BA=E9=97=B4?= =?UTF-8?q?=E5=85=8D=E5=AD=97=E7=AC=A6=E4=B8=B2=E5=BE=80=E8=BF=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- crates/oxide_builtins/src/iterator.rs | 6 +-- crates/oxide_builtins/src/json.rs | 15 +++--- crates/oxide_builtins/src/object.rs | 51 +++++++++++++------- crates/oxide_builtins/src/reflect.rs | 4 +- crates/oxide_builtins/src/typed_array.rs | 15 ++++-- crates/oxide_types/src/private_key.rs | 59 +++++++++++++++++++++++- crates/oxide_vm/src/dispatch/misc.rs | 4 +- crates/oxide_vm/src/promise.rs | 9 ++-- crates/oxide_vm/src/vm.rs | 57 ++++++++++++++++++++++- crates/oxide_vm/src/vm_dispatch_misc.rs | 39 ++++++++++------ 10 files changed, 199 insertions(+), 60 deletions(-) diff --git a/crates/oxide_builtins/src/iterator.rs b/crates/oxide_builtins/src/iterator.rs index 67ddc6a..24355e8 100644 --- a/crates/oxide_builtins/src/iterator.rs +++ b/crates/oxide_builtins/src/iterator.rs @@ -1,6 +1,6 @@ use oxide_kernel::shape_forge::EMPTY_SHAPE_ID; use oxide_types::object::JsObject; -use oxide_types::private_key::make_well_known_symbol_key; +use oxide_types::private_key::{make_int_key, make_well_known_symbol_key}; use oxide_types::value::JsValue; use oxide_runtime_api::{to_object, NativeResult, VmHost}; @@ -289,8 +289,8 @@ fn next_array_like( let arr = unsafe { &*inner.as_js_object_ptr() }; if index < arr.prop_count() as usize { // 数组元素读取走 GetValue:普通数据属性返回槽值,访问器属性 - // (defineProperty getter)触发 getter 并透传异常。 - let key_si = vm.kernel_core().perm_interner().intern(&index.to_string()).0; + // (defineProperty getter)触发 getter 并透传异常。整数键免 intern。 + let key_si = make_int_key(index as u32); let value = match vm.ordinary_get(arr, key_si, inner) { Ok(v) => v, Err(err) => { diff --git a/crates/oxide_builtins/src/json.rs b/crates/oxide_builtins/src/json.rs index f9e96e6..3daeb42 100644 --- a/crates/oxide_builtins/src/json.rs +++ b/crates/oxide_builtins/src/json.rs @@ -3,9 +3,10 @@ use std::fmt::Write; use oxide_kernel::shape_forge::EMPTY_SHAPE_ID; use oxide_types::object::JsObject; +use oxide_types::private_key::make_int_key; use oxide_types::value::JsValue; -use crate::object::walk_own_keys; +use crate::object::{key_si_to_string, walk_own_keys}; use oxide_runtime_api::{NativeResult, VmHost}; @@ -74,8 +75,7 @@ fn walk_reviver( if obj.is_array() { let len = obj.prop_count() as usize; for i in 0..len { - let index_str = i.to_string(); - let child_si = vm.kernel_core().perm_interner().intern(&index_str).0; + let child_si = make_int_key(i as u32); walk_reviver(vm, obj_ptr, child_si, reviver)?; } // 子节点可能已改写父级,重新读取当前值。 @@ -92,9 +92,8 @@ fn walk_reviver( } // 对当前值调用 reviver,用返回值覆盖属性槽。 - let kc = vm.kernel_core().clone(); - let key_str = kc.perm_interner().lookup(key_si).unwrap_or(""); - let key_val = vm.new_string(key_str); + let key_str = key_si_to_string(vm, key_si); + let key_val = vm.new_string(&key_str); let holder_val = JsValue::from_js_object(holder_ptr); match vm.call_function_sync(reviver, holder_val, &[key_val, val]) { Ok(new_val) => { @@ -362,12 +361,10 @@ fn stringify_object( let keys = walk_own_keys(vm, obj); // 预计算 (名称, 槽位) 对,并应用白名单过滤。 - let kc = vm.kernel_core().clone(); - let perm_interner = kc.perm_interner(); let entries: Vec<(String, u32)> = keys .into_iter() .filter_map(|(si, pos)| { - let name = perm_interner.lookup(si).unwrap_or("").to_string(); + let name = key_si_to_string(vm, si); if let Some(whitelist) = replacer_whitelist { if !whitelist.contains(&name) { return None; diff --git a/crates/oxide_builtins/src/object.rs b/crates/oxide_builtins/src/object.rs index 03b0626..43ca825 100644 --- a/crates/oxide_builtins/src/object.rs +++ b/crates/oxide_builtins/src/object.rs @@ -2,7 +2,7 @@ use oxide_kernel::shape_forge::{ShapeForge, EMPTY_SHAPE_ID}; use oxide_kernel::string_forge::PermInterner; use oxide_types::object::{JsObject, PropAttributes, PropMetaEntry}; use oxide_types::private_key::{ - is_private_name_key, is_symbol_key, make_well_known_symbol_key, symbol_index_from_key, + int_key_value, is_int_key, is_private_name_key, is_symbol_key, make_well_known_symbol_key, symbol_index_from_key, well_known_symbol_id_from_key, }; use oxide_types::value::JsValue; @@ -53,18 +53,37 @@ pub fn walk_own_keys(vm: &H, obj: &JsObject) -> Vec<(u32, u32)> { pos += 1; } keys.sort_by(|(a_si, _), (b_si, _)| { - let a_str = vm.kernel_core().perm_interner().lookup(*a_si).unwrap_or(""); - let b_str = vm.kernel_core().perm_interner().lookup(*b_si).unwrap_or(""); - match (is_integer_index(a_str), is_integer_index(b_str)) { - (true, true) => a_str.parse::().unwrap().cmp(&b_str.parse::().unwrap()), - (true, false) => std::cmp::Ordering::Less, - (false, true) => std::cmp::Ordering::Greater, - (false, false) => std::cmp::Ordering::Equal, + // 整数键(含 INT 区间键)以数值升序排在普通字符串键之前。 + let a_idx = int_or_string_index(vm, *a_si); + let b_idx = int_or_string_index(vm, *b_si); + match (a_idx, b_idx) { + (Some(ai), Some(bi)) => ai.cmp(&bi), + (Some(_), None) => std::cmp::Ordering::Less, + (None, Some(_)) => std::cmp::Ordering::Greater, + (None, None) => std::cmp::Ordering::Equal, } }); keys } +/// 键 si 的数组下标值:整数键直接反解,字符串键查 interner 后判是否规范数字串。 +fn int_or_string_index(vm: &H, si: u32) -> Option { + if is_int_key(si) { + return Some(int_key_value(si)); + } + let key = vm.kernel_core().perm_interner().lookup(si)?; + is_integer_index(key).then(|| key.parse::().unwrap()) +} + +/// 键 si 物化为字符串文本:整数键反解数字串,其余查 interner。 +pub fn key_si_to_string(vm: &H, si: u32) -> String { + if is_int_key(si) { + int_key_value(si).to_string() + } else { + vm.kernel_core().perm_interner().lookup(si).unwrap_or("").to_string() + } +} + /// 收集对象自身全部 Symbol 键(shape 链),按键序排列(根→叶,即插入序)。 /// 与 [`walk_own_keys`] 互补:只返回 Symbol 键,供 `getOwnPropertySymbols` 使用。 fn walk_own_symbol_keys(vm: &H, obj: &JsObject) -> Vec<(u32, u32)> { @@ -156,6 +175,9 @@ pub fn object_get_own_property_symbols(vm: &mut H, args: &[u8]) -> Na /// 字符串键是否为数组下标("0"~"4294967294",无前导零)。 fn array_index_of(vm: &H, key_si: u32) -> Option { + if is_int_key(key_si) { + return Some(int_key_value(key_si)); + } let key = vm.kernel_core().perm_interner().lookup(key_si)?; if key.is_empty() || (key.len() > 1 && key.starts_with('0')) { return None; @@ -356,10 +378,7 @@ pub fn object_keys(vm: &mut H, args: &[u8]) -> NativeResult { .unwrap_or(PropAttributes::DEFAULT_DATA.enumerable()) }) .collect(); - key_names = owned_keys - .iter() - .map(|(si, _offset)| vm.kernel_core().perm_interner().lookup(*si).unwrap_or("").to_string()) - .collect(); + key_names = owned_keys.iter().map(|(si, _offset)| key_si_to_string(vm, *si)).collect(); } let n = key_names.len(); let array_proto = vm.session().builtin_world().array_proto.as_ptr() as *mut JsObject; @@ -883,9 +902,7 @@ pub fn object_get_own_property_names(vm: &mut H, args: &[u8]) -> Nati let key_names: Vec = { let obj = unsafe { &*obj_ptr }; let keys = walk_own_keys(vm, obj); - keys.iter() - .map(|(si, _)| vm.kernel_core().perm_interner().lookup(*si).unwrap_or("").to_string()) - .collect() + keys.iter().map(|(si, _)| key_si_to_string(vm, *si)).collect() }; let n = key_names.len(); @@ -1128,8 +1145,8 @@ pub fn object_entries(vm: &mut H, args: &[u8]) -> NativeResult { vm.epoch().bump(), )); for (i, (si, offset)) in owned_keys.iter().enumerate() { - let key_str = vm.kernel_core().perm_interner().lookup(*si).unwrap_or_default(); - let key_val = vm.new_string(key_str); + let key_str = key_si_to_string(vm, *si); + let key_val = vm.new_string(&key_str); let val = obj.get_prop_at(*offset); let pair = vm.alloc_object(JsObject::new_array( EMPTY_SHAPE_ID, diff --git a/crates/oxide_builtins/src/reflect.rs b/crates/oxide_builtins/src/reflect.rs index 234ac91..4cf28ed 100644 --- a/crates/oxide_builtins/src/reflect.rs +++ b/crates/oxide_builtins/src/reflect.rs @@ -3,7 +3,7 @@ use oxide_types::mem::P; use oxide_types::object::{JsObject, PropAttributes}; use oxide_types::value::JsValue; -use crate::object::{delete_own_property, walk_own_keys}; +use crate::object::{delete_own_property, key_si_to_string, walk_own_keys}; use oxide_runtime_api::{NativeResult, VmHost}; @@ -193,7 +193,7 @@ pub fn reflect_own_keys(vm: &mut H, args: &[u8]) -> NativeResult { let target = unsafe { &*target_ptr }; let key_names: Vec = walk_own_keys(vm, target) .into_iter() - .map(|(si, _)| vm.kernel_core().perm_interner().lookup(si).unwrap_or("").to_string()) + .map(|(si, _)| key_si_to_string(vm, si)) .collect(); NativeResult::Ok(make_string_array(vm, &key_names)) } diff --git a/crates/oxide_builtins/src/typed_array.rs b/crates/oxide_builtins/src/typed_array.rs index 1cf31d1..16daca0 100644 --- a/crates/oxide_builtins/src/typed_array.rs +++ b/crates/oxide_builtins/src/typed_array.rs @@ -1,5 +1,6 @@ use oxide_kernel::shape_forge::EMPTY_SHAPE_ID; use oxide_types::object::{JsObject, NativeFnPtr, TypedArrayKind}; +use oxide_types::private_key::{int_key_value, is_int_key}; use oxide_types::value::JsValue; use crate::array_buffer::{array_buffer_data_ptr, new_array_buffer, MAX_ARRAY_BUFFER_LENGTH}; @@ -214,11 +215,15 @@ pub fn typed_array_integer_index(vm: &mut H, obj: &JsObject, prop_nam return None; } let view = unsafe { *ptr }; - let key = vm.kernel_core().perm_interner().lookup(prop_name_si)?; - if key.is_empty() || (key.len() > 1 && key.starts_with('0')) { - return None; - } - let index = key.parse::().ok()?; + let index = if is_int_key(prop_name_si) { + int_key_value(prop_name_si) + } else { + let key = vm.kernel_core().perm_interner().lookup(prop_name_si)?; + if key.is_empty() || (key.len() > 1 && key.starts_with('0')) { + return None; + } + key.parse::().ok()? + }; Some((index as usize, view.length)) } diff --git a/crates/oxide_types/src/private_key.rs b/crates/oxide_types/src/private_key.rs index 12b46c9..f5e2eb0 100644 --- a/crates/oxide_types/src/private_key.rs +++ b/crates/oxide_types/src/private_key.rs @@ -1,18 +1,51 @@ -//! 私有名(private name)与 Symbol 属性键编码。 +//! 私有名(private name)、Symbol 与整数属性键编码。 //! //! ECMAScript 私有字段(`#x`)与 `Symbol` 使用全局注册符号以外的独立命名空间。 //! 本模块用 `u32` 键的高半区表示它们,与普通属性名区分,避免与用户可见的字符串键冲突: +//! - 整数键:`[INT_KEY_BASE, PRIVATE_NAME_BASE)`,`make_int_key` 生成(非负小整数 +//! 属性键直接编码,免字符串转换与 intern); //! - 私有名键:`[PRIVATE_NAME_BASE, SYMBOL_KEY_BASE)`,`make_private_name_id` 生成; //! - Symbol 键:`>= SYMBOL_KEY_BASE`,其中最低 `WELL_KNOWN_SYMBOL_COUNT` 个槽位 //! 保留给 well-known symbol,其余编码用户 symbol 的 VM 内下标。 //! -//! Symbol 键不被字符串 interner 收录,枚举路径须按 `is_symbol_key` 显式跳过。 +//! Symbol 键与整数键不被字符串 interner 收录,枚举路径须按 `is_symbol_key` / +//! `is_int_key` 显式跳过或物化文本。 /// 私有名键区间的起始值。 /// /// 键值 `>= PRIVATE_NAME_BASE` 一律视为私有名。 pub const PRIVATE_NAME_BASE: u32 = 0x8000_0000; +/// 整数键区间的起始值。 +/// +/// 整数键编码 `INT_KEY_BASE + i`(`i < INT_KEY_COUNT`),落在 +/// `[INT_KEY_BASE, PRIVATE_NAME_BASE)` 空档——与字符串 interner id +/// (自 0 递增,实际值远小于该起点)及 private/symbol 区间互不重叠。 +pub const INT_KEY_BASE: u32 = 0x4000_0000; + +/// 整数键可覆盖的索引个数(`2^30`,远大于 dense 数组上限)。 +pub const INT_KEY_COUNT: u32 = 0x4000_0000; + +/// 把非负小整数索引编码为整数属性键(与 [`int_key_value`] 互逆)。 +/// +/// 调用方须保证 `i < INT_KEY_COUNT`,否则溢出到 private 区间。 +#[inline] +pub const fn make_int_key(i: u32) -> u32 { + INT_KEY_BASE + i +} + +/// 判断键是否为整数键(位于整数键区间)。 +#[inline] +pub const fn is_int_key(key: u32) -> bool { + key >= INT_KEY_BASE && key < PRIVATE_NAME_BASE +} + +/// 从整数键反解原索引(与 [`make_int_key`] 互逆)。 +#[inline] +pub const fn int_key_value(key: u32) -> u32 { + key - INT_KEY_BASE +} + /// Symbol 键区间的起始值,避开私有名区间。 pub const SYMBOL_KEY_BASE: u32 = 0xA000_0000; @@ -133,4 +166,26 @@ mod tests { assert_eq!(symbol_index_from_key(key), SYMBOL_INDEX_MASK); assert!(is_symbol_key(key)); } + + #[test] + fn int_keys_stay_in_own_band() { + assert_eq!(make_int_key(0), INT_KEY_BASE); + assert_eq!(int_key_value(INT_KEY_BASE), 0); + assert_eq!(int_key_value(make_int_key(123)), 123); + assert!(is_int_key(make_int_key(0))); + assert!(!is_int_key(0)); + assert!(!is_int_key(PRIVATE_NAME_BASE)); + assert!(!is_private_name_key(make_int_key(0))); + assert!(!is_symbol_key(make_int_key(0))); + assert_ne!(make_int_key(5), make_private_name_id(5)); + assert_ne!(make_int_key(5), make_symbol_key(5)); + } + + #[test] + fn int_key_roundtrip_max() { + let key = make_int_key(INT_KEY_COUNT - 1); + assert!(is_int_key(key)); + assert!(key < PRIVATE_NAME_BASE); + assert_eq!(int_key_value(key), INT_KEY_COUNT - 1); + } } diff --git a/crates/oxide_vm/src/dispatch/misc.rs b/crates/oxide_vm/src/dispatch/misc.rs index 3ea7c9d..4e780b0 100644 --- a/crates/oxide_vm/src/dispatch/misc.rs +++ b/crates/oxide_vm/src/dispatch/misc.rs @@ -3,6 +3,7 @@ use crate::{vm_error, vm_trace}; use oxide_bytecode::opcode; use oxide_kernel::shape_forge::EMPTY_SHAPE_ID; use oxide_types::object::{JsObject, PropAttributes}; +use oxide_types::private_key::make_int_key; use oxide_types::value::JsValue; impl Vm { @@ -171,8 +172,9 @@ impl Vm { obj.type_tag = JsObject::OBJ_TYPE_ARGUMENTS; // 索引属性:按实参下标写入 shape 槽,属性描述符为默认(可写/可枚举/可配置)。 + // 下标走整数键,保证 `arguments[0]`(property_key_si(int 0))键等价命中。 for i in 0..count as usize { - let si = self.kernel_core.perm_interner().intern(&i.to_string()).0; + let si = make_int_key(i as u32); let val = self.spill_stack.get(base as usize + i).copied().unwrap_or(JsValue::undefined()); self.set_or_create_prop_value(obj, si, val); } diff --git a/crates/oxide_vm/src/promise.rs b/crates/oxide_vm/src/promise.rs index 98297bc..1dcd9c3 100644 --- a/crates/oxide_vm/src/promise.rs +++ b/crates/oxide_vm/src/promise.rs @@ -11,6 +11,7 @@ use oxide_kernel::shape_forge::EMPTY_SHAPE_ID; use oxide_runtime_api::NativeResult; use oxide_types::mem::P; use oxide_types::object::{JsObject, NativeFnPtr, PropAttributes}; +use oxide_types::private_key::make_int_key; use oxide_types::value::JsValue; use crate::native::NativeFn; @@ -1451,7 +1452,7 @@ fn promise_all_resolve_element(vm: &mut Vm, args: &[u8]) -> NativeResult { let value = if args.len() > 1 { vm.reg(args[1]) } else { JsValue::undefined() }; let values = agg_record_val(vm, record, AGG_VALUES_PROP); if values.is_object() { - let key_si = vm.kernel_core.perm_interner().intern(&index.to_string()).0; + let key_si = make_int_key(index as u32); // SAFETY: values 是存活数组对象;直写元素区不触发数组 setter。 vm.set_or_create_prop_value(unsafe { &mut *values.as_js_object_ptr() }, key_si, value); } @@ -1474,7 +1475,7 @@ fn promise_all_settled_resolve_element(vm: &mut Vm, args: &[u8]) -> NativeResult let values = agg_record_val(vm, record, AGG_VALUES_PROP); if values.is_object() { let settled = vm.make_settled_record("fulfilled", "value", value); - let key_si = vm.kernel_core.perm_interner().intern(&index.to_string()).0; + let key_si = make_int_key(index as u32); // SAFETY: values 是存活数组对象。 vm.set_or_create_prop_value(unsafe { &mut *values.as_js_object_ptr() }, key_si, settled); } @@ -1495,7 +1496,7 @@ fn promise_all_settled_reject_element(vm: &mut Vm, args: &[u8]) -> NativeResult let values = agg_record_val(vm, record, AGG_VALUES_PROP); if values.is_object() { let settled = vm.make_settled_record("rejected", "reason", reason); - let key_si = vm.kernel_core.perm_interner().intern(&index.to_string()).0; + let key_si = make_int_key(index as u32); // SAFETY: values 是存活数组对象。 vm.set_or_create_prop_value(unsafe { &mut *values.as_js_object_ptr() }, key_si, settled); } @@ -1515,7 +1516,7 @@ fn promise_any_reject_element(vm: &mut Vm, args: &[u8]) -> NativeResult { let reason = if args.len() > 1 { vm.reg(args[1]) } else { JsValue::undefined() }; let errors = agg_record_val(vm, record, AGG_VALUES_PROP); if errors.is_object() { - let key_si = vm.kernel_core.perm_interner().intern(&index.to_string()).0; + let key_si = make_int_key(index as u32); // SAFETY: errors 是存活数组对象。 vm.set_or_create_prop_value(unsafe { &mut *errors.as_js_object_ptr() }, key_si, reason); } diff --git a/crates/oxide_vm/src/vm.rs b/crates/oxide_vm/src/vm.rs index ab1596b..13c821c 100644 --- a/crates/oxide_vm/src/vm.rs +++ b/crates/oxide_vm/src/vm.rs @@ -20,11 +20,39 @@ use oxide_runtime_api::NativeResult; use oxide_types::error::{JsError, JsErrorKind}; use oxide_types::mem::{Epoch, P}; use oxide_types::object::{Cell, JsObject, NativeFnPtr, PropAttributes}; -use oxide_types::private_key::{make_symbol_key, make_well_known_symbol_key}; +use oxide_types::private_key::{ + int_key_value, is_int_key, make_int_key, make_symbol_key, make_well_known_symbol_key, INT_KEY_COUNT, +}; use oxide_types::value::{JsValue, PTR_MASK}; pub(crate) const MAX_PROTO_CHAIN_DEPTH: usize = 1024; +/// 判定字符串是否为规范数组下标(无前导零的纯数字串),并反解其值。 +/// +/// 命中则把该字符串键与对应整数键合并为同一键(`obj["5"]` == `obj[5]`)。 +/// 只覆盖 `[0, INT_KEY_COUNT)`,更大的数字串(含 2^32 边界)走普通字符串键。 +fn canonical_index_of(s: &str) -> Option { + if s.is_empty() { + return None; + } + let b = s.as_bytes(); + if !b[0].is_ascii_digit() { + return None; + } + if b.len() > 1 && b[0] == b'0' { + return None; + } + if !b.iter().all(|c| c.is_ascii_digit()) { + return None; + } + let v: u32 = s.parse().ok()?; + if v < INT_KEY_COUNT { + Some(v) + } else { + None + } +} + /// 将 [`NativeFnPtr`] 转换为可调用的 [`NativeFn`]。 /// /// # Safety @@ -900,10 +928,32 @@ impl Vm { } } + /// 把 `JsValue` 转为属性键 si(`u32`)。 + /// + /// 非负小整数(含整值 double)直接编码到整数键区间,免 to_string + intern; + /// 字符串中形如数组下标的规范数字串(`"5"`)映射到同一整数键,保证 + /// `obj["5"]` 与 `obj[5]` 键等价。 + /// + /// # 边界与前提 + /// - 索引超 `INT_KEY_COUNT`(2^30)时回退字符串键路径(intern + 反查仍正确) + /// - 负数与小数不进入整数键区间(`arr[-1]`/`arr[1.5]` 是普通字符串键) pub(crate) fn property_key_si(&mut self, val: JsValue) -> Result { - if val.is_string() { + if val.is_int() { + let i = val.as_int(); + if i >= 0 && (i as u32) < INT_KEY_COUNT { + return Ok(make_int_key(i as u32)); + } + } else if val.is_double() { + let d = val.as_double(); + if d >= 0.0 && d.fract() == 0.0 && d < INT_KEY_COUNT as f64 { + return Ok(make_int_key(d as u32)); + } + } else if val.is_string() { // SAFETY: val 是字符串值,把其内容桥接为永久 key id。 let s = unsafe { &(*val.as_string_ptr()).data }; + if let Some(i) = canonical_index_of(s) { + return Ok(make_int_key(i)); + } return Ok(self.kernel_core.perm_interner().intern(s).0); } // Symbol 值直接编码为 Symbol 键(不进字符串 interner,键相互独立)。 @@ -929,6 +979,9 @@ impl Vm { } pub(crate) fn array_index_from_property_key(&self, prop_name_si: u32) -> Option { + if is_int_key(prop_name_si) { + return Some(int_key_value(prop_name_si)); + } let key = self.kernel_core.perm_interner().lookup(prop_name_si)?; if key.is_empty() || (key.len() > 1 && key.starts_with('0')) { return None; diff --git a/crates/oxide_vm/src/vm_dispatch_misc.rs b/crates/oxide_vm/src/vm_dispatch_misc.rs index 08c727d..d83950e 100644 --- a/crates/oxide_vm/src/vm_dispatch_misc.rs +++ b/crates/oxide_vm/src/vm_dispatch_misc.rs @@ -5,7 +5,7 @@ use crate::vm::{native_fn_ptr_to_fn, CallFrame, ForInIter, FrameContinuation, Vm use crate::vm_trace; use oxide_runtime_api::{to_boolean, to_string_full, NativeResult}; use oxide_types::object::{JsObject, PropAttributes}; -use oxide_types::private_key::{is_private_name_key, is_symbol_key}; +use oxide_types::private_key::{int_key_value, is_int_key, is_private_name_key, is_symbol_key, make_int_key}; use oxide_types::value::JsValue; impl Vm { @@ -321,8 +321,7 @@ impl Vm { return self.raise_type_error("for-in right-hand side is not an object"); } - let mut keys_vec: bumpalo::collections::Vec<(JsValue, u32)> = - bumpalo::collections::Vec::new_in(self.epoch.bump()); + let mut keys_vec: Vec<(JsValue, u32)> = Vec::new(); let mut seen = std::collections::HashSet::new(); let mut current = obj_val; @@ -341,8 +340,11 @@ impl Vm { .map(|m| m.attributes.enumerable()) .unwrap_or(PropAttributes::DEFAULT_DATA.enumerable()); if is_enum { - let idx = self.kernel_core.perm_interner().intern(&i.to_string()).0; - keys_vec.push((JsValue::perm_string(self.kernel_core.perm_interner().string_ptr(idx)), idx)); + // 整数下标编码为整数键(免 intern + 防永久泄漏);枚举值 + // 物化为数字字符串,排序时由 array_index_from_property_key + // 直接反解。 + let idx = make_int_key(i); + keys_vec.push((self.new_string(&i.to_string()), idx)); } } } @@ -380,10 +382,13 @@ impl Vm { .map(|meta| meta.attributes.enumerable()) .unwrap_or(PropAttributes::DEFAULT_DATA.enumerable()); if enumerable { - keys_vec.push(( - JsValue::perm_string(self.kernel_core.perm_interner().string_ptr(shape.property_name)), - shape.property_name, - )); + // 整数键不在 interner:物化为数字字符串;其余键取永久串。 + let key_val = if is_int_key(shape.property_name) { + self.new_string(&int_key_value(shape.property_name).to_string()) + } else { + JsValue::perm_string(self.kernel_core.perm_interner().string_ptr(shape.property_name)) + }; + keys_vec.push((key_val, shape.property_name)); } } cursor = shape.parent; @@ -407,7 +412,11 @@ impl Vm { } }); - let iter = self.epoch.alloc(ForInIter { keys: keys_vec, index: 0 }); + // std Vec 建完后迁入 bump 区,避免枚举循环期间对 self 的 &mut 借用 + // 与 bump 借用冲突。 + let keys_bump: bumpalo::collections::Vec<(JsValue, u32)> = + bumpalo::collections::Vec::from_iter_in(keys_vec.into_iter(), self.epoch.bump()); + let iter = self.epoch.alloc(ForInIter { keys: keys_bump, index: 0 }); self.iters.push_for_in(iter.cast::>()); Ok(()) } @@ -736,7 +745,7 @@ impl Vm { let code_units: Vec = unsafe { (*src.as_string_ptr()).data.encode_utf16().collect() }; for (i, unit) in code_units.iter().enumerate() { let s = char::from_u32(*unit as u32).map(|c| c.to_string()).unwrap_or_default(); - let si = self.kernel_core.perm_interner().intern(&i.to_string()).0; + let si = make_int_key(i as u32); let ch_val = self.new_string(&s); let rest = unsafe { &mut *rest_ptr }; self.set_or_create_prop_value(rest, si, ch_val); @@ -790,7 +799,7 @@ impl Vm { let code_units: Vec = s.encode_utf16().collect(); for (i, unit) in code_units.iter().enumerate() { let ch = char::from_u32(*unit as u32).map(|c| c.to_string()).unwrap_or_default(); - let si = self.kernel_core.perm_interner().intern(&i.to_string()).0; + let si = make_int_key(i as u32); assignments.push((si, self.new_string(&ch))); } } @@ -806,7 +815,7 @@ impl Vm { .map(|m| m.attributes.enumerable()) .unwrap_or(PropAttributes::DEFAULT_DATA.enumerable()); if enumerable { - let si = self.kernel_core.perm_interner().intern(&i.to_string()).0; + let si = make_int_key(i); let val = match self.ordinary_get(src_obj, si, src) { Ok(v) => v, Err(e) => return self.raise_call_error(&e).map(|_| ()), @@ -885,7 +894,7 @@ impl Vm { let code_units: Vec = unsafe { (*src.as_string_ptr()).data.encode_utf16().collect() }; for (i, unit) in code_units.iter().enumerate() { let s = char::from_u32(*unit as u32).map(|c| c.to_string()).unwrap_or_default(); - let si = self.kernel_core.perm_interner().intern(&i.to_string()).0; + let si = make_int_key(i as u32); let ch_val = self.new_string(&s); self.set_or_create_prop_value(target, si, ch_val); } @@ -914,7 +923,7 @@ impl Vm { .map(|m| m.attributes.enumerable()) .unwrap_or(PropAttributes::DEFAULT_DATA.enumerable()); if enumerable { - let si = self.kernel_core.perm_interner().intern(&i.to_string()).0; + let si = make_int_key(i); let val = self.ordinary_get(src_obj, si, src)?; assignments.push((si, val)); }