mirror of
https://github.com/pjh456/OxideJS.git
synced 2026-10-23 18:21:13 +08:00
263 lines
9.9 KiB
Rust
263 lines
9.9 KiB
Rust
//! member 复合写(obj.x += 1 / obj.x++ 等)接入 IC 写命中的行为测试。
|
||
//!
|
||
//! 覆盖三组语义:①写侧命中(depth==0 槽直写,读写对称);②写新属性走
|
||
//! CreateDataProperty 快路径(shape 转换,命中无法覆盖);③继承属性 shadow /
|
||
//! 原型 setter / 只读继承属性(写侧 depth==0 限制与原型链检查兜底,防原型污染)。
|
||
//!
|
||
//! 计数断言基于单次 run 内 miss/hit 对比(run() 每次冷启动,IC 学习不跨 run)。
|
||
|
||
use std::sync::Arc;
|
||
|
||
use oxide_compiler::compiler::Compiler;
|
||
use oxide_parser::Allocator;
|
||
use oxide_vm::vm::Vm;
|
||
|
||
/// 编译并单次运行,返回 (结果, hits, misses)。
|
||
fn run_once(source: &str) -> (String, u64, u64) {
|
||
let allocator = Allocator::default();
|
||
let program = oxide_parser::parse(&allocator, source).expect("parse");
|
||
let module = Compiler::new().compile(&program).expect("compile");
|
||
let mut vm = Vm::new();
|
||
let result = vm.run(&Arc::new(module)).expect("run");
|
||
// 字符串结果经 lookup_str 提取内容(JsValue Display 只打印 {string})。
|
||
let r = vm.lookup_str(result).unwrap_or_else(|| format!("{result}"));
|
||
(r, vm.ic_hit_count(), vm.ic_miss_count())
|
||
}
|
||
|
||
/// 单态 member 复合写:写侧命中直写(读侧命中不计数,pre-existing 口径——
|
||
/// 计数仅来自写侧与独立 IC_GET/IC_SET 分发站点)。
|
||
#[test]
|
||
fn member_compound_monomorphic_write_hits() {
|
||
let (r, hits, misses) = run_once(
|
||
r#"var t = { x: 0 };
|
||
for (var i = 0; i < 10000; i++) { t.x += 1; }
|
||
t.x"#,
|
||
);
|
||
assert_eq!(r, "10000");
|
||
assert_eq!(misses, 1, "仅末尾 `t.x` IC_GET 站点学习 miss,循环写侧零 miss");
|
||
assert_eq!(hits, 10000, "循环 10000 次写侧全命中(实际 {hits})");
|
||
}
|
||
|
||
/// 4 shape 轮换 member 复合写:写侧复用读侧学习的 4 槽条目,循环内零写 miss。
|
||
#[test]
|
||
fn member_compound_poly4_read_write_hits() {
|
||
let (r, hits, misses) = run_once(
|
||
r#"var objs = [{x:0}, {x:0,y:1}, {x:0,z:1}, {x:0,y:1,z:2}];
|
||
for (var i = 0; i < 10000; i++) {
|
||
var t = objs[i % 4];
|
||
t.x += 1;
|
||
}
|
||
objs[0].x + objs[1].x + objs[2].x + objs[3].x"#,
|
||
);
|
||
assert_eq!(r, "10000", "四对象 x 各增 2500 次");
|
||
assert_eq!(misses, 4, "末尾 4 个 `objs[i].x` IC_GET 站点各学习 1 次");
|
||
assert_eq!(hits, 10000, "循环写侧 10000 全命中(实际 {hits})");
|
||
}
|
||
|
||
/// 继承属性 shadow(核心正确性):写侧只接受 depth==0 槽,原型不被污染。
|
||
#[test]
|
||
fn inherited_prop_shadows_receiver_not_proto() {
|
||
let (r, _hits, _misses) = run_once(
|
||
r#"var p = { x: 1 };
|
||
var o = Object.create(p);
|
||
o.x += 1;
|
||
o.x + ":" + p.x"#,
|
||
);
|
||
assert_eq!(r, "2:1", "o 获得 own x=2,p.x 保持 1(未直写原型)");
|
||
}
|
||
|
||
/// IC_SET 独立赋值路径的继承 shadow:`Object.create(p).x = 5` 同样不写原型。
|
||
#[test]
|
||
fn ic_set_inherited_shadow_receiver_only() {
|
||
let (r, _hits, _misses) = run_once(
|
||
r#"var p = { x: 1 };
|
||
var o = Object.create(p);
|
||
o.x = 5;
|
||
p.x"#,
|
||
);
|
||
assert_eq!(r, "1", "p.x 保持 1,o 得到 own x=5");
|
||
}
|
||
|
||
/// 原型 accessor setter:IC_SET 与 member 复合写都触发 setter,接收者不建新槽。
|
||
#[test]
|
||
fn proto_setter_called_both_write_paths() {
|
||
let (r, _hits, _misses) = run_once(
|
||
r#"var calls = 0;
|
||
var p = { set x(v) { calls++; } };
|
||
var o = Object.create(p);
|
||
o.x = 5;
|
||
o.x += 2;
|
||
calls + ":" + Object.keys(o).length"#,
|
||
);
|
||
assert_eq!(r, "2:0", "setter 被调 2 次(IC_SET 与 member 各 1),o 无 own 属性");
|
||
}
|
||
|
||
/// 严格模式原型只读 data:两条写路径都按 ordinary_set 抛 TypeError。
|
||
#[test]
|
||
fn readonly_inherited_data_raises_in_strict() {
|
||
let (r, _hits, _misses) = run_once(
|
||
r#"var p = {};
|
||
Object.defineProperty(p, 'x', { value: 1, writable: false });
|
||
var o = Object.create(p);
|
||
var msg = '';
|
||
(function() { 'use strict';
|
||
try { o.x = 5; } catch (e) { msg = e.message; }
|
||
try { o.x += 1; } catch (e) { msg += '|' + e.message; }
|
||
})();
|
||
msg"#,
|
||
);
|
||
assert_eq!(
|
||
r, "cannot assign to read-only property|cannot assign to read-only property",
|
||
"两条路径都拒绝写只读继承属性"
|
||
);
|
||
}
|
||
|
||
/// sloppy 原型只读 data:两条写路径都静默 no-op(值不变、不抛)。
|
||
#[test]
|
||
fn readonly_inherited_data_silently_fails_in_sloppy() {
|
||
let (r, _hits, _misses) = run_once(
|
||
r#"var p = {};
|
||
Object.defineProperty(p, 'x', { value: 1, writable: false });
|
||
var o = Object.create(p);
|
||
var threw = false;
|
||
try { o.x = 5; } catch (e) { threw = true; }
|
||
try { o.x += 1; } catch (e) { threw = true; }
|
||
threw + ':' + o.x + ':' + o.hasOwnProperty('x')"#,
|
||
);
|
||
assert_eq!(r, "false:1:false", "sloppy 只读继承属性写静默失败,值保持 1,不创建 own 属性");
|
||
}
|
||
|
||
/// 写新属性(shape 转换):快路径建 shape + 写回,循环内同 shape 写全命中。
|
||
#[test]
|
||
fn write_new_prop_then_hit() {
|
||
let (r, hits, misses) = run_once(
|
||
r#"var o = {};
|
||
o.y = 0;
|
||
for (var i = 0; i < 10000; i++) { o.y += 1; }
|
||
o.y"#,
|
||
);
|
||
assert_eq!(r, "10000");
|
||
assert_eq!(misses, 2, "`o.y = 0` 站点 1 次 + 末尾 `o.y` IC_GET 站点 1 次");
|
||
assert_eq!(hits, 10000, "compound 写侧 10000 全命中(实际 {hits})");
|
||
}
|
||
|
||
/// ic_cache.js 同款模式(批量建对象 + 分批写 y/z 新属性):快路径建 shape 正确。
|
||
#[test]
|
||
fn ic_cache_style_new_prop_writes() {
|
||
let (r, _hits, _misses) = run_once(
|
||
r#"var objs = [];
|
||
for (var i = 0; i < 100; i++) {
|
||
objs.push({ x: i });
|
||
if (i % 3 === 0) objs[i].y = i;
|
||
if (i % 5 === 0) objs[i].z = i;
|
||
}
|
||
var yok = 0, zok = 0;
|
||
for (var i = 0; i < 100; i++) {
|
||
if (i % 3 === 0 && objs[i].y === i) yok++;
|
||
if (i % 5 === 0 && objs[i].z === i) zok++;
|
||
}
|
||
yok + ":" + zok"#,
|
||
);
|
||
assert_eq!(r, "34:20", "y/z 全部写入成功(含 i%15==0 的 y+z 同对象场景)");
|
||
}
|
||
|
||
/// has_prop_meta 对象 member 写走 ordinary_set(不缓存,accessor 语义保留)。
|
||
#[test]
|
||
fn meta_object_member_write_uses_ordinary_set() {
|
||
let (r, _hits, _misses) = run_once(
|
||
r#"var store = 1;
|
||
var o = { get a() { return store; }, set a(v) { store = v; } };
|
||
o.a += 5;
|
||
store"#,
|
||
);
|
||
assert_eq!(r, "6", "accessor 对象走 setter,store 更新为 6");
|
||
}
|
||
|
||
/// 命中率口径:member 写 hit/miss 计入后 ic_hit_rate 反映写侧全量命中。
|
||
#[test]
|
||
fn member_write_counts_in_ic_hit_rate() {
|
||
let allocator = Allocator::default();
|
||
let program = oxide_parser::parse(
|
||
&allocator,
|
||
r#"var t = { x: 0 };
|
||
for (var i = 0; i < 1000; i++) { t.x += 1; }
|
||
t.x"#,
|
||
)
|
||
.expect("parse");
|
||
let module = Compiler::new().compile(&program).expect("compile");
|
||
let mut vm = Vm::new();
|
||
let result = vm.run(&Arc::new(module)).expect("run");
|
||
let r = vm.lookup_str(result).unwrap_or_else(|| format!("{result}"));
|
||
assert_eq!(r, "1000");
|
||
// 循环写侧 1000 命中 + 末尾 IC_GET 1 miss → 命中率 ~0.999(写侧计入后趋近真实)。
|
||
assert!(vm.ic_hit_rate() > 0.99, "member 写侧命中计入命中率(实际 {:.4})", vm.ic_hit_rate());
|
||
}
|
||
|
||
/// `__proto__` 赋值 + 继承成员复合写:原型链建立正常,shadow 语义不变(回归锚定)。
|
||
#[test]
|
||
fn proto_assignment_then_compound_shadow() {
|
||
let (r, _hits, _misses) = run_once(
|
||
r#"var p = { x: 1 };
|
||
var o = {};
|
||
o.__proto__ = p;
|
||
o.x += 1;
|
||
o.x + ":" + p.x"#,
|
||
);
|
||
assert_eq!(r, "2:1", "__proto__ 赋值建链后复合写 shadow 到 o,p 不被污染");
|
||
}
|
||
|
||
/// 数组 length 复合写分流 ordinary_set(ArraySetLength):`arr.length += 1` 更新逻辑
|
||
/// 长度而非建影子槽,后续 push / 索引写读正常(P1-1 回归锚定)。
|
||
#[test]
|
||
fn array_length_compound_write_uses_array_set_length() {
|
||
let (r, _hits, _misses) = run_once(
|
||
r#"var arr = [1, 2, 3];
|
||
arr.length += 1;
|
||
var l1 = arr.length;
|
||
arr.push(5);
|
||
arr[1] = 9;
|
||
l1 + ":" + arr.length + ":" + arr.join(",")"#,
|
||
);
|
||
assert_eq!(r, "4:5:1,9,3,,5", "length 复合写后 l1=4,push 后 length=5,元素区正确");
|
||
}
|
||
|
||
/// 数组 length 复合写后再直接赋值 `arr.length = N`:两写路径均走 ArraySetLength,
|
||
/// 元素区按新长度伸缩,不残留影子槽(P1-1 双路径对称回归锚定)。
|
||
#[test]
|
||
fn array_length_compound_then_direct_assign() {
|
||
let (r, _hits, _misses) = run_once(
|
||
r#"var arr = [1, 2, 3];
|
||
arr.length += 1;
|
||
arr.length = 5;
|
||
var l = arr.length;
|
||
arr[4] = 7;
|
||
l + ":" + arr.join(",")"#,
|
||
);
|
||
assert_eq!(r, "5:1,2,3,,7", "length=5 后逻辑长度与元素区同步伸缩");
|
||
}
|
||
|
||
/// 数组写新命名属性(IC_SET 快路径建 shape 槽):命名槽在元素区之后,元素写入不错位。
|
||
#[test]
|
||
fn array_named_prop_create_then_element_write() {
|
||
let (r, _hits, _misses) = run_once(
|
||
r#"var arr = [1];
|
||
arr.foo = 1;
|
||
arr[0] = 9;
|
||
arr.foo"#,
|
||
);
|
||
assert_eq!(r, "1", "元素区写入后命名属性 foo 不错位(槽在元素区之后)");
|
||
}
|
||
|
||
/// 数组命名属性 member 复合写命中 shadow 槽 + 元素增长后读回正确(P2-1)。
|
||
#[test]
|
||
fn array_named_prop_compound_hit_after_element_growth() {
|
||
let (r, _hits, _misses) = run_once(
|
||
r#"var arr = [1];
|
||
arr.foo = 2;
|
||
arr.foo += 3;
|
||
arr[0] = 9;
|
||
arr.foo"#,
|
||
);
|
||
assert_eq!(r, "5", "member 复合写命中 shadow 槽(2+3=5),元素增长后仍不错位");
|
||
}
|