perf: 字符串 length 站点指针快判免 IC 读

This commit is contained in:
2026-10-05 05:25:26 +00:00
parent 8e6703e606
commit bd14e332ba
7 changed files with 135 additions and 20 deletions
Generated
+18 -18
View File
@@ -1095,7 +1095,7 @@ dependencies = [
[[package]]
name = "oxide_builtins"
version = "0.7.0"
version = "0.8.0"
dependencies = [
"chrono",
"fast-float",
@@ -1120,7 +1120,7 @@ dependencies = [
[[package]]
name = "oxide_bytecode"
version = "0.7.0"
version = "0.8.0"
dependencies = [
"num-bigint",
"oxide_log",
@@ -1128,7 +1128,7 @@ dependencies = [
[[package]]
name = "oxide_cfg"
version = "0.7.0"
version = "0.8.0"
dependencies = [
"oxide_bytecode",
"oxide_emit",
@@ -1139,7 +1139,7 @@ dependencies = [
[[package]]
name = "oxide_cli"
version = "0.7.0"
version = "0.8.0"
dependencies = [
"ansi_term",
"bumpalo",
@@ -1159,7 +1159,7 @@ dependencies = [
[[package]]
name = "oxide_code_cache"
version = "0.7.0"
version = "0.8.0"
dependencies = [
"lru",
"oxide_bytecode",
@@ -1168,7 +1168,7 @@ dependencies = [
[[package]]
name = "oxide_compiler"
version = "0.7.0"
version = "0.8.0"
dependencies = [
"oxide_bytecode",
"oxide_cfg",
@@ -1187,7 +1187,7 @@ dependencies = [
[[package]]
name = "oxide_dce"
version = "0.7.0"
version = "0.8.0"
dependencies = [
"oxide_bytecode",
"oxide_cfg",
@@ -1201,7 +1201,7 @@ dependencies = [
[[package]]
name = "oxide_emit"
version = "0.7.0"
version = "0.8.0"
dependencies = [
"num-bigint",
"oxc_allocator",
@@ -1216,7 +1216,7 @@ dependencies = [
[[package]]
name = "oxide_ir"
version = "0.7.0"
version = "0.8.0"
dependencies = [
"oxide_bytecode",
"oxide_log",
@@ -1225,7 +1225,7 @@ dependencies = [
[[package]]
name = "oxide_kernel"
version = "0.7.0"
version = "0.8.0"
dependencies = [
"bumpalo",
"dashmap",
@@ -1240,7 +1240,7 @@ dependencies = [
[[package]]
name = "oxide_liveness"
version = "0.7.0"
version = "0.8.0"
dependencies = [
"oxide_bytecode",
"oxide_cfg",
@@ -1252,7 +1252,7 @@ dependencies = [
[[package]]
name = "oxide_log"
version = "0.7.0"
version = "0.8.0"
dependencies = [
"tracing",
"tracing-appender",
@@ -1261,7 +1261,7 @@ dependencies = [
[[package]]
name = "oxide_parser"
version = "0.7.0"
version = "0.8.0"
dependencies = [
"oxc_allocator",
"oxc_ast",
@@ -1276,7 +1276,7 @@ dependencies = [
[[package]]
name = "oxide_regalloc"
version = "0.7.0"
version = "0.8.0"
dependencies = [
"oxide_bytecode",
"oxide_cfg",
@@ -1292,7 +1292,7 @@ dependencies = [
[[package]]
name = "oxide_runtime_api"
version = "0.7.0"
version = "0.8.0"
dependencies = [
"num-bigint",
"num-traits",
@@ -1304,7 +1304,7 @@ dependencies = [
[[package]]
name = "oxide_test262"
version = "0.7.0"
version = "0.8.0"
dependencies = [
"oxide_bytecode",
"oxide_compiler",
@@ -1321,7 +1321,7 @@ dependencies = [
[[package]]
name = "oxide_types"
version = "0.7.0"
version = "0.8.0"
dependencies = [
"bumpalo",
"hashbrown 0.14.5",
@@ -1333,7 +1333,7 @@ dependencies = [
[[package]]
name = "oxide_vm"
version = "0.7.0"
version = "0.8.0"
dependencies = [
"bumpalo",
"chrono",
+1 -1
View File
@@ -22,7 +22,7 @@ members = [
]
[workspace.package]
version = "0.7.0"
version = "0.8.0"
edition = "2021"
license = "MIT"
rust-version = "1.80"
+18
View File
@@ -334,6 +334,16 @@ impl Vm {
let val = self.regs[a];
let obj_ptr = val.as_object_ptr() as *mut JsObject;
if obj_ptr.is_null() {
// 入口快判:键寄存器恒为 perm 串,指针相等即键为 "length";
// 字符串接收者直返码元长度,免键解析(hash64 + DashMap + RwLock)。
let key_val = self.regs[b];
if key_val.is_string() && key_val.as_string_ptr() == self.length_perm_ptr && val.is_string() {
// SAFETY: val 是字符串值。
let len = unsafe { (*val.as_string_ptr()).utf16_len() };
self.regs[a] = JsValue::int(len as i32);
self.pc += IC_EXT_WORDS;
return Ok(());
}
let prop_name_si = self.property_key_si(self.regs[b])?;
if let Some(resolved) = self.primitive_property_get(val, prop_name_si)? {
self.regs[a] = resolved;
@@ -350,6 +360,14 @@ impl Vm {
}
let obj = unsafe { &*obj_ptr };
// 入口快判:键寄存器恒为 perm 串,指针相等即键为 "length";
// 数组接收者直返逻辑长度,免 IC 读与键解析(与虚拟长度槽口径一致)。
let key_val = self.regs[b];
if key_val.is_string() && key_val.as_string_ptr() == self.length_perm_ptr && obj.is_array() {
self.regs[a] = obj.logical_len_value();
self.pc += IC_EXT_WORDS;
return Ok(());
}
let (cached_shape_id, cached_slot, cached_depth) = ic_helper::read_ic_slot0(&self.bytecode, &mut self.pc);
let ic_pc = self.pc;
if obj.has_prop_meta() {
+5 -1
View File
@@ -22,7 +22,7 @@ use crate::vm_state::{GcState, IterState, ProfilingState, SampleState, SymbolSta
use oxide_kernel::kernel::{KernelCore, KernelSession};
use oxide_types::error::JsErrorKind;
use oxide_types::mem::P;
use oxide_types::object::{Cell, JsObject, NativeFnPtr};
use oxide_types::object::{Cell, JsObject, JsString, NativeFnPtr};
use oxide_types::private_key::INT_KEY_COUNT;
use oxide_types::value::JsValue;
@@ -179,6 +179,10 @@ pub struct Vm {
/// KernelCore 不重建),属性 get/set 热路径免每次 intern(hash64 + DashMap +
/// RwLock 读锁)。
pub(crate) length_si: u32,
/// `"length"` 属性键 perm 串的内部指针:IC 站点的键寄存器恒为 perm 串
/// (发射层字符串常量统一经 perm_string 物化),指针相等即键为 "length",
/// 入口快判只比指针免键解析。PermInterner 追加式、指针唯一,永不失效。
pub(crate) length_perm_ptr: *const JsString,
pub object_prototype: P<JsObject>,
/// `%GeneratorPrototype%`:生成器实例的原型(next/return/throw 方法挂此)。
pub generator_proto: P<JsObject>,
+76
View File
@@ -1864,4 +1864,80 @@ mod tests {
assert_eq!(obj.prop_vec_len(), 1, "命名区恰一个槽,复写不重复建槽");
assert_eq!(vm.ordinary_get(obj, si, a).expect("get"), JsValue::int(9));
}
// ===== defineProperty 对 length 键的拦截语义钉 =====
#[test]
fn define_property_array_length_applies_array_set_length() {
let mut vm = Vm::new();
let a = new_array_val(&mut vm, 3);
unsafe {
let arr = a.as_js_object_ptr();
(*arr).set_prop_at(0, JsValue::int(1));
(*arr).set_prop_at(1, JsValue::int(2));
(*arr).set_prop_at(2, JsValue::int(3));
}
let length_si = vm.kernel_core.perm_interner().intern("length").0;
let si0 = array_index_si(&mut vm, 0);
let si1 = array_index_si(&mut vm, 1);
// 数组 length 一律 ArraySetLength 拦截:截断到 1,元素区同步收缩。
// writable 保持 true,允许后续重定义再扩。
let attrs = PropAttributes::new(true, false, false);
vm.define_data_property(unsafe { &mut *a.as_js_object_ptr() }, length_si, JsValue::int(1), attrs)
.expect("define length");
let obj = unsafe { &*a.as_js_object_ptr() };
assert_eq!(obj.logical_len(), 1, "length 截断到 1");
assert_eq!(vm.ordinary_get(obj, length_si, a).expect("get"), JsValue::int(1));
assert_eq!(vm.ordinary_get(obj, si0, a).expect("get"), JsValue::int(1));
assert_eq!(vm.ordinary_get(obj, si1, a).expect("get"), JsValue::undefined(), "元素 1 随截断删除");
// 扩到 5:首元素保留,新增元素区补 undefined。
vm.define_data_property(unsafe { &mut *a.as_js_object_ptr() }, length_si, JsValue::int(5), attrs)
.expect("define length");
let obj = unsafe { &*a.as_js_object_ptr() };
assert_eq!(obj.logical_len(), 5, "length 扩到 5");
assert_eq!(vm.ordinary_get(obj, length_si, a).expect("get"), JsValue::int(5));
assert_eq!(vm.ordinary_get(obj, si0, a).expect("get"), JsValue::int(1), "首元素保留");
}
#[test]
fn define_property_array_length_accessor_rejected() {
let mut vm = Vm::new();
let a = new_array_val(&mut vm, 1);
let getter = native_function(&mut vm, native_return_7);
let length_si = vm.kernel_core.perm_interner().intern("length").0;
// 数组 length 为不可配置数据属性,禁止转访问器。
let err = vm
.define_accessor_property(
unsafe { &mut *a.as_js_object_ptr() },
length_si,
getter,
JsValue::undefined(),
PropAttributes::new(false, false, false),
)
.expect_err("数组 length 禁转访问器");
assert!(err.contains("cannot redefine"), "实际: {err}");
let obj = unsafe { &*a.as_js_object_ptr() };
assert_eq!(obj.logical_len(), 1, "length 不变");
}
#[test]
fn define_property_string_length_rejected() {
let mut vm = Vm::new();
let str_si = vm.kernel_core.perm_interner().intern("abcd").0;
let str_val = JsValue::perm_string(vm.kernel_core.perm_interner().string_ptr(str_si));
let boxed = coercion::to_object(str_val, &mut vm).expect("box");
let length_si = vm.kernel_core.perm_interner().intern("length").0;
// 字符串对象 length 不可写不可配置,重定义校验拒绝。
let err = vm
.define_data_property(
unsafe { &mut *boxed.as_js_object_ptr() },
length_si,
JsValue::int(5),
PropAttributes::new(false, false, false),
)
.expect_err("字符串 length 不可重定义");
assert!(err.contains("cannot redefine"), "实际: {err}");
let obj = unsafe { &*boxed.as_js_object_ptr() };
assert_eq!(vm.ordinary_get(obj, length_si, boxed).expect("get"), JsValue::int(4), "length 不变");
}
}
+4
View File
@@ -43,6 +43,7 @@ impl Vm {
// 在 builtin 绑定之后取 id:绑定过程已 intern "length",此处命中缓存得到
// 稳定的 id,用于运行期常见属性(如函数 length 槽)的快路径。
let length_si = core.perm_interner().intern("length").0;
let length_perm_ptr = core.perm_interner().string_ptr(length_si);
let obj_proto = P::clone(&session.builtin_world().object_proto);
// 提前缓存执行期字符串 GC 初始水位(构造后 config 不再变化)。
let gc_threshold = core.config().session_gc_threshold;
@@ -55,6 +56,7 @@ impl Vm {
kernel_core: core,
session,
length_si,
length_perm_ptr,
object_prototype: obj_proto,
generator_proto: P::new(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::null())),
generator_function_proto: P::new(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::null())),
@@ -182,6 +184,7 @@ impl Vm {
// 在 builtin 绑定之后取 id:绑定过程已 intern "length",此处命中缓存得到
// 稳定的 id,用于运行期常见属性(如函数 length 槽)的快路径。
let length_si = core.perm_interner().intern("length").0;
let length_perm_ptr = core.perm_interner().string_ptr(length_si);
let obj_proto = P::clone(&session.builtin_world().object_proto);
// 提前缓存执行期字符串 GC 初始水位(构造后 config 不再变化)。
let gc_threshold = core.config().session_gc_threshold;
@@ -194,6 +197,7 @@ impl Vm {
kernel_core: core,
session,
length_si,
length_perm_ptr,
object_prototype: obj_proto,
generator_proto: P::new(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::null())),
generator_function_proto: P::new(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::null())),
+13
View File
@@ -0,0 +1,13 @@
var ITERATIONS = 3000;
var arr = [];
for (var i = 0; i < 100; i++) {
arr.push("item" + i);
}
var sum = 0;
for (var j = 0; j < ITERATIONS; j++) {
for (var k = 0; k < 100; k++) {
sum += arr[k].length;
}
}
sum