mirror of
https://github.com/pjh456/OxideJS.git
synced 2026-10-25 03:01:12 +08:00
perf: 数组整数键走独立 si 区间免字符串往返
This commit is contained in:
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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<u32> {
|
||||
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<u32, String> {
|
||||
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<u32> {
|
||||
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;
|
||||
|
||||
@@ -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::<ForInIter<'static>>());
|
||||
Ok(())
|
||||
}
|
||||
@@ -736,7 +745,7 @@ impl Vm {
|
||||
let code_units: Vec<u16> = 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<u16> = 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<u16> = 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));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user