From bd14e332bab28170b1b8a65b0096119c0ae3c08b Mon Sep 17 00:00:00 2001 From: pjh456 <147148383@qq.com> Date: Mon, 5 Oct 2026 05:25:26 +0000 Subject: [PATCH] =?UTF-8?q?perf:=20=E5=AD=97=E7=AC=A6=E4=B8=B2=20length=20?= =?UTF-8?q?=E7=AB=99=E7=82=B9=E6=8C=87=E9=92=88=E5=BF=AB=E5=88=A4=E5=85=8D?= =?UTF-8?q?=20IC=20=E8=AF=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Cargo.lock | 36 +++++------ Cargo.toml | 2 +- crates/oxide_vm/src/dispatch/property.rs | 18 ++++++ crates/oxide_vm/src/vm/mod.rs | 6 +- crates/oxide_vm/src/vm_props.rs | 76 ++++++++++++++++++++++++ crates/oxide_vm/src/vm_support.rs | 4 ++ tests/stress/string_length.js | 13 ++++ 7 files changed, 135 insertions(+), 20 deletions(-) create mode 100644 tests/stress/string_length.js diff --git a/Cargo.lock b/Cargo.lock index a9148e76..c0c62862 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -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", diff --git a/Cargo.toml b/Cargo.toml index bd702fe5..800976eb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,7 +22,7 @@ members = [ ] [workspace.package] -version = "0.7.0" +version = "0.8.0" edition = "2021" license = "MIT" rust-version = "1.80" diff --git a/crates/oxide_vm/src/dispatch/property.rs b/crates/oxide_vm/src/dispatch/property.rs index aae4edaa..c5e28636 100644 --- a/crates/oxide_vm/src/dispatch/property.rs +++ b/crates/oxide_vm/src/dispatch/property.rs @@ -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() { diff --git a/crates/oxide_vm/src/vm/mod.rs b/crates/oxide_vm/src/vm/mod.rs index 4301ea52..7143752c 100644 --- a/crates/oxide_vm/src/vm/mod.rs +++ b/crates/oxide_vm/src/vm/mod.rs @@ -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, /// `%GeneratorPrototype%`:生成器实例的原型(next/return/throw 方法挂此)。 pub generator_proto: P, diff --git a/crates/oxide_vm/src/vm_props.rs b/crates/oxide_vm/src/vm_props.rs index dcec1331..954a55ef 100644 --- a/crates/oxide_vm/src/vm_props.rs +++ b/crates/oxide_vm/src/vm_props.rs @@ -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 不变"); + } } diff --git a/crates/oxide_vm/src/vm_support.rs b/crates/oxide_vm/src/vm_support.rs index d7d051d5..28a8efc0 100644 --- a/crates/oxide_vm/src/vm_support.rs +++ b/crates/oxide_vm/src/vm_support.rs @@ -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())), diff --git a/tests/stress/string_length.js b/tests/stress/string_length.js new file mode 100644 index 00000000..09739f66 --- /dev/null +++ b/tests/stress/string_length.js @@ -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