From cd9132fb14d3e86157ce9f52f4d40d5a4c7322ff Mon Sep 17 00:00:00 2001 From: pjh456 <147148383@qq.com> Date: Thu, 13 Aug 2026 18:12:49 +0800 Subject: [PATCH] =?UTF-8?q?perf:=20=E6=95=B0=E7=BB=84=E8=BF=AD=E4=BB=A3?= =?UTF-8?q?=E6=96=B9=E6=B3=95=E5=85=83=E7=B4=A0=E8=AF=BB=E5=8F=96=E8=B5=B0?= =?UTF-8?q?=E5=AF=86=E9=9B=86=E7=9B=B4=E8=AF=BB=E5=BF=AB=E8=B7=AF=E5=BE=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- crates/oxide_builtins/src/array.rs | 10 +++- crates/oxide_vm/tests/builtins_array_tests.rs | 51 +++++++++++++++++++ 2 files changed, 60 insertions(+), 1 deletion(-) diff --git a/crates/oxide_builtins/src/array.rs b/crates/oxide_builtins/src/array.rs index ce68eecf..c4266a93 100644 --- a/crates/oxide_builtins/src/array.rs +++ b/crates/oxide_builtins/src/array.rs @@ -109,10 +109,18 @@ fn get_this_arraylike(vm: &mut H, val: JsValue) -> Result<(*mut JsObj /// 从 arraylike 读取 index 位置元素(按规范 Get:访问器/原型链/异常完整传播)。 #[inline(always)] fn arraylike_get(vm: &mut H, ptr: *mut JsObject, i: usize) -> Result { + // 真数组密集直读快路径:无元素 meta(无 hole / accessor,O(1) 空指针判定)且 + // 索引在元素区内时,直接读数据槽,免每元素构造数字串 + 属性键转换。 + // 语义与 ordinary_get 一致:`array_elements_meta_vec().is_none()` 保证该索引 + // 恒为数据属性(见 vm_props.rs ordinary_get_inner 数组分支)。 + let obj = unsafe { &*ptr }; + if obj.is_array() && obj.array_elements_meta_vec().is_none() && i < obj.array_prop_count as usize { + return Ok(obj.get_prop_at(i)); + } let key_str = vm.new_string(&i.to_string()); let key_si = vm.property_key_si(key_str); let recv = JsValue::from_js_object(ptr); - match vm.ordinary_get(unsafe { &*ptr }, key_si, recv) { + match vm.ordinary_get(obj, key_si, recv) { Ok(v) => Ok(v), Err(msg) => Err(from_engine_error(vm, &msg)), } diff --git a/crates/oxide_vm/tests/builtins_array_tests.rs b/crates/oxide_vm/tests/builtins_array_tests.rs index 40d663c9..a2dc6192 100644 --- a/crates/oxide_vm/tests/builtins_array_tests.rs +++ b/crates/oxide_vm/tests/builtins_array_tests.rs @@ -354,3 +354,54 @@ fn array_fill_single() { let obj = unsafe { &*result.as_js_object_ptr() }; assert_eq!(obj.get_prop_at(0).as_int(), 0); } + +#[test] +fn array_iteration_hole_reads_undefined() { + // 稀疏数组(字面量空洞 / delete)元素读取落到 undefined:引擎对 hole 不跳过 + // 回调而是读 undefined(pre-existing 语义,直读 fast path 与慢路径一致)。 + let (vm, result) = eval("[10,,30].map(x=>x*2).join(',')").unwrap(); + assert_eq!(to_str(&vm, result), "20,NaN,60"); + + // delete 产生的 hole 经 map 读 undefined,不残留洞前值。 + let (vm, result) = eval("var a=[1,2,3]; delete a[1]; a.map(x=>x+1).join(',')").unwrap(); + assert_eq!(to_str(&vm, result), "2,NaN,4"); + + // reduce 对 hole 累加 undefined。 + let (vm, result) = eval("var a=[1,,3]; a.reduce((s,x)=>s+','+x,'')").unwrap(); + assert_eq!(to_str(&vm, result), ",1,undefined,3"); +} + +#[test] +fn array_iteration_element_accessor_still_invoked() { + // 数组索引定义 getter 后,map/find 等每元素读取必须触发访问器而非直读数据槽。 + let (vm, result) = + eval("var a=[1,2]; Object.defineProperty(a,0,{get:()=>99, configurable:true}); a.map(x=>x*10).join(',')") + .unwrap(); + assert_eq!(to_str(&vm, result), "990,20"); +} + +#[test] +fn array_iteration_arraylike_receiver_non_array() { + // Array.prototype 方法在非真数组(arraylike)上调用的 receiver 差异:走属性键慢路径。 + let (vm, result) = eval("Array.prototype.map.call({0:'a',1:'b',length:2}, x=>x+x).join(',')").unwrap(); + assert_eq!(to_str(&vm, result), "aa,bb"); + + let (vm, result) = eval("Array.prototype.join.call({0:'a',1:'b',length:2}, '-')").unwrap(); + assert_eq!(to_str(&vm, result), "a-b"); +} + +#[test] +fn array_iteration_dense_elements_read_correct() { + // 真数组密集直读 fast path 覆盖各迭代方法的基本取值。 + let (vm, result) = eval("[10,20,30].map(x=>x*2).join(',')").unwrap(); + assert_eq!(to_str(&vm, result), "20,40,60"); + + let (_vm, result) = eval("[1,2].includes(2)").unwrap(); + assert_eq!(result, JsValue::bool(true)); + + let (_vm, result) = eval("[1,2].indexOf(1)").unwrap(); + assert_num_eq(result, 0.0); + + let (vm, result) = eval("[1,2,3].slice(1).join(',')").unwrap(); + assert_eq!(to_str(&vm, result), "2,3"); +}