Files
OxideJS-New/crates/oxide_vm/tests/property_array_fastpath_tests.rs
T
pjh123 ee4a12668f perf: 数组元素访问 dispatch 内联 fast path + length_si 缓存
GET_PROP_DYNAMIC/SET_PROP_DYNAMIC 开头内联数组元素 fast path:整数键 +
真数组 + 无元素 meta + 界内时直读/直写元素区,免 primitive_property_get /
ordinary_get/set 调用链与 __proto__ 每次 RwLock 查询;越界与有 meta
场景回退原路径(CreateDataProperty + length 扩展 / accessor/hole 协议)。
GET 侧 is_object() 快检先于 checked_object_ptr,保持原始值 receiver
不抛错。length_si 缓存为 Vm 字段(builtin 绑定后取 id,稳定非 0),
普通属性路径 5 处 hot spot 改读缓存,消除每次 intern 的共享锁开销。
2026-08-15 04:58:57 +08:00

159 lines
6.0 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//! 03.2 数组元素访问 dispatch 内联 fast path 语义测试:验证 fast path 命中
//! (密集数组 int 键界内直读写)与各回退场景(越界、hole、accessor、非数组、
//! 非对象 receiver)与 ordinary 属性路径行为一致。
use oxide_compiler::compiler::Compiler;
use oxide_types::value::JsValue;
use oxide_vm::vm::Vm;
fn eval(source: &str) -> Result<(Vm, JsValue), String> {
let allocator = oxide_parser::Allocator::default();
let program = oxide_parser::parse(&allocator, source).map_err(|e| format!("Parse error: {:?}", e))?;
let module = Compiler::new().compile(&program).map_err(|e| format!("Compile error: {}", e))?;
let mut vm = Vm::new();
let result = vm.run(&module)?;
Ok((vm, result))
}
fn eval_str(source: &str) -> Result<String, String> {
let (_vm, v) = eval(source)?;
if v.is_string() {
Ok(oxide_runtime_api::to_string(v))
} else {
Ok(format!("{v}"))
}
}
// 密集数组整数键界内读:命中 fast path 直读元素区。
#[test]
fn dense_index_read_hits_fast_path() {
assert_eq!(eval_str("var a=[1,2,3]; a[1]").unwrap(), "2");
assert_eq!(eval_str("var a=[10,20,30]; a[0]+a[2]").unwrap(), "40");
}
// 密集数组整数键界内写:命中 fast path 直写元素区,读回一致。
#[test]
fn dense_index_write_hits_fast_path() {
assert_eq!(eval_str("var a=[1,2,3]; a[1]=9; a[1]").unwrap(), "9");
assert_eq!(eval_str("var a=[1,2,3]; a[0]=5; a[0]").unwrap(), "5");
}
// 字符串规范数字键与整数键等价(`a["1"]` == `a[1]`,同一 fast path 键)。
#[test]
fn string_canonical_index_key_equivalent() {
assert_eq!(eval_str("var a=[1,2,3]; a[\"1\"]").unwrap(), "2");
assert_eq!(eval_str("var a=[1,2,3]; a[\"1\"]=7; a[1]").unwrap(), "7");
}
// 整值 double 键(`a[1.0]`)与整数键等价。
#[test]
fn integral_double_key_equivalent() {
assert_eq!(eval_str("var a=[1,2,3]; a[1.0]").unwrap(), "2");
}
// 越界读不命中 fast path,落原型链(undefined / 原型上元素)。
#[test]
fn out_of_bounds_read_falls_to_proto_chain() {
assert_eq!(eval_str("var a=[1,2,3]; a[5]").unwrap(), "undefined");
assert_eq!(eval_str("var a=[1,2,3]; a.__proto__={5:\"x\"}; a[5]").unwrap(), "x");
assert_eq!(eval_str("Array.prototype[9]=\"proto9\"; var a=[1,2,3]; a[9]").unwrap(), "proto9");
}
// 越界写不命中 fast path,走 CreateDataProperty + length 扩展。
#[test]
fn out_of_bounds_write_extends_length() {
assert_eq!(eval_str("var a=[1,2,3]; a[5]=50; a.length").unwrap(), "6");
assert_eq!(eval_str("var a=[1,2,3]; a[5]=50; a[5]").unwrap(), "50");
}
// delete 后的 hole:元素 meta 非空,不命中 fast path,读落原型链。
#[test]
fn deleted_hole_read_falls_to_proto() {
assert_eq!(eval_str("var a=[1,2,3]; delete a[1]; a[1]").unwrap(), "undefined");
}
// defineProperty accessor:元素 meta 非空,不命中 fast path,getter/setter 仍触发。
#[test]
fn accessor_getter_and_setter_still_trigger() {
assert_eq!(
eval_str("var a=[1,2,3]; Object.defineProperty(a,1,{get:function(){return 99;}}); a[1]").unwrap(),
"99"
);
assert_eq!(
eval_str(
"var a=[1,2,3]; Object.defineProperty(a,1,{set:function(v){this._s=v;},get:function(){return this._s;}}); a[1]=7; a[1]"
)
.unwrap(),
"7"
);
}
// 非写属性(writable:false)写入:fast path 不命中,走完整协议抛 TypeError。
#[test]
fn non_writable_index_write_throws() {
let r = eval_str(
"var a=[1,2,3]; Object.defineProperty(a,0,{writable:false,value:1}); try { a[0]=9; 'no' } catch(e) { e instanceof TypeError ? 'throws' : 'wrong' }"
)
.unwrap();
assert_eq!(r, "throws", "expected TypeError, got {r:?}");
}
// 非规范数字串(前导零)不映射整数键,走字符串键慢路径(不命中 fast path)。
#[test]
fn non_canonical_index_string_uses_slow_path() {
assert_eq!(eval_str("var a=[1,2,3]; a[\"05\"]").unwrap(), "undefined");
assert_eq!(eval_str("var a=[1,2,3]; a[\"05\"]=9; a.length").unwrap(), "3");
}
// 负索引/小数键不进入整数键区间,走字符串键慢路径。
#[test]
fn negative_and_fractional_keys_use_slow_path() {
assert_eq!(eval_str("var a=[1,2,3]; a[-1]").unwrap(), "undefined");
assert_eq!(eval_str("var a=[1,2,3]; a[1.5]").unwrap(), "undefined");
}
// 普通对象(非数组)整数键不受 fast path 影响,走普通属性路径。
#[test]
fn plain_object_int_key_unaffected() {
assert_eq!(eval_str("var o={0:\"x\"}; o[0]").unwrap(), "x");
assert_eq!(eval_str("var o={}; o[0]=\"y\"; o[0]").unwrap(), "y");
}
// 非对象 receiver(`"abc"[0]`):is_object 快检先行,不 raise、返回 undefined。
#[test]
fn primitive_string_index_does_not_raise() {
assert_eq!(eval_str("\"abc\"[0]").unwrap(), "undefined");
assert_eq!(eval_str("var a=\"abc\"; a[0]").unwrap(), "undefined");
}
// push 构造的数组 fast path 读写与 length 一致。
#[test]
fn push_built_array_fast_path_consistent() {
assert_eq!(eval_str("var a=[]; a.push(1); a.push(2); a.length").unwrap(), "2");
assert_eq!(eval_str("var a=[]; a.push(1); a.push(2); a[0]+a[1]").unwrap(), "3");
}
// TypedArray 整数索引不命中数组 fast path(is_array 为假),走 TA 分支。
#[test]
fn typed_array_int_index_unaffected() {
assert_eq!(eval_str("var t=new Int32Array([5,6,7]); t[1]").unwrap(), "6");
}
// fast path 写后 length 读一致(shadow length 同步不受影响)。
#[test]
fn fast_path_write_keeps_length_in_sync() {
assert_eq!(eval_str("var a=[1,2,3]; a[0]=7; a.length").unwrap(), "3");
}
// 空数组 / 稀疏空洞(字面量 hole,无 meta)读 undefined,与慢路径一致。
#[test]
fn sparse_literal_hole_reads_undefined() {
assert_eq!(eval_str("var a=[10,,30]; a[1]").unwrap(), "undefined");
}
// 越界写后读取原型链元素(fast path 不越界直写)。
#[test]
fn oob_write_then_proto_read() {
assert_eq!(eval_str("Array.prototype[9]=\"proto9\"; var a=[1,2,3]; a[9]=90; a[9]").unwrap(), "90");
}