mirror of
https://github.com/LadybirdBrowser/ladybird
synced 2026-04-26 01:35:08 +02:00
1573 lines
41 KiB
JavaScript
1573 lines
41 KiB
JavaScript
describe("basic property access", () => {
|
|
test("basic own property get", () => {
|
|
const obj = { x: 1, y: 2, z: 3 };
|
|
expect(obj.x).toBe(1);
|
|
expect(obj.y).toBe(2);
|
|
expect(obj.z).toBe(3);
|
|
});
|
|
|
|
test("basic own property set", () => {
|
|
const obj = { x: 1 };
|
|
obj.x = 42;
|
|
expect(obj.x).toBe(42);
|
|
});
|
|
|
|
test("property get in loop (IC should kick in)", () => {
|
|
const obj = { value: 100 };
|
|
let sum = 0;
|
|
for (let i = 0; i < 1000; i++) {
|
|
sum += obj.value;
|
|
}
|
|
expect(sum).toBe(100000);
|
|
});
|
|
|
|
test("property set in loop (IC should kick in)", () => {
|
|
const obj = { counter: 0 };
|
|
for (let i = 0; i < 1000; i++) {
|
|
obj.counter = i;
|
|
}
|
|
expect(obj.counter).toBe(999);
|
|
});
|
|
});
|
|
|
|
describe("prototype chain access", () => {
|
|
test("single prototype chain get", () => {
|
|
const proto = { inherited: 42 };
|
|
const obj = Object.create(proto);
|
|
obj.own = 1;
|
|
expect(obj.inherited).toBe(42);
|
|
expect(obj.own).toBe(1);
|
|
});
|
|
|
|
test("prototype chain get in loop", () => {
|
|
const proto = { value: 5 };
|
|
const obj = Object.create(proto);
|
|
let sum = 0;
|
|
for (let i = 0; i < 1000; i++) {
|
|
sum += obj.value;
|
|
}
|
|
expect(sum).toBe(5000);
|
|
});
|
|
|
|
test("deep prototype chain (3 levels)", () => {
|
|
const grandparent = { a: 1 };
|
|
const parent = Object.create(grandparent);
|
|
parent.b = 2;
|
|
const child = Object.create(parent);
|
|
child.c = 3;
|
|
|
|
expect(child.a).toBe(1);
|
|
expect(child.b).toBe(2);
|
|
expect(child.c).toBe(3);
|
|
});
|
|
|
|
test("deep prototype chain (10 levels)", () => {
|
|
let obj = { level: 0, base: "base" };
|
|
for (let i = 1; i <= 10; i++) {
|
|
obj = Object.create(obj);
|
|
obj.level = i;
|
|
}
|
|
expect(obj.level).toBe(10);
|
|
expect(obj.base).toBe("base");
|
|
});
|
|
|
|
test("prototype chain with null prototype", () => {
|
|
const obj = Object.create(null);
|
|
obj.x = 1;
|
|
expect(obj.x).toBe(1);
|
|
expect(obj.toString).toBeUndefined();
|
|
});
|
|
|
|
test("shadowing prototype property", () => {
|
|
const proto = { x: 1 };
|
|
const obj = Object.create(proto);
|
|
expect(obj.x).toBe(1);
|
|
obj.x = 2;
|
|
expect(obj.x).toBe(2);
|
|
expect(proto.x).toBe(1);
|
|
});
|
|
|
|
test("shadowing in loop", () => {
|
|
const proto = { value: 100 };
|
|
const objects = [];
|
|
for (let i = 0; i < 100; i++) {
|
|
const obj = Object.create(proto);
|
|
if (i % 2 === 0) obj.value = i;
|
|
objects.push(obj);
|
|
}
|
|
expect(objects[0].value).toBe(0);
|
|
expect(objects[1].value).toBe(100);
|
|
expect(objects[50].value).toBe(50);
|
|
expect(objects[51].value).toBe(100);
|
|
});
|
|
});
|
|
|
|
describe("shape transitions", () => {
|
|
test("adding properties changes shape", () => {
|
|
const obj = {};
|
|
obj.a = 1;
|
|
obj.b = 2;
|
|
obj.c = 3;
|
|
expect(obj.a).toBe(1);
|
|
expect(obj.b).toBe(2);
|
|
expect(obj.c).toBe(3);
|
|
});
|
|
|
|
test("same shape objects in loop", () => {
|
|
const objects = [];
|
|
for (let i = 0; i < 100; i++) {
|
|
objects.push({ x: i, y: i * 2 });
|
|
}
|
|
let sum = 0;
|
|
for (const obj of objects) {
|
|
sum += obj.x + obj.y;
|
|
}
|
|
expect(sum).toBe(14850);
|
|
});
|
|
|
|
test("different shape objects in loop (IC polymorphism)", () => {
|
|
const objects = [];
|
|
for (let i = 0; i < 100; i++) {
|
|
if (i % 3 === 0) objects.push({ x: i });
|
|
else if (i % 3 === 1) objects.push({ x: i, y: 1 });
|
|
else objects.push({ x: i, y: 1, z: 2 });
|
|
}
|
|
let sum = 0;
|
|
for (const obj of objects) {
|
|
sum += obj.x;
|
|
}
|
|
expect(sum).toBe(4950);
|
|
});
|
|
|
|
test("megamorphic IC (many different shapes)", () => {
|
|
const objects = [];
|
|
for (let i = 0; i < 100; i++) {
|
|
const obj = {};
|
|
for (let j = 0; j <= i % 20; j++) {
|
|
obj["prop" + j] = j;
|
|
}
|
|
obj.common = i;
|
|
objects.push(obj);
|
|
}
|
|
let sum = 0;
|
|
for (const obj of objects) {
|
|
sum += obj.common;
|
|
}
|
|
expect(sum).toBe(4950);
|
|
});
|
|
});
|
|
|
|
describe("property deletion", () => {
|
|
test("delete own property", () => {
|
|
const obj = { x: 1, y: 2 };
|
|
expect(obj.x).toBe(1);
|
|
delete obj.x;
|
|
expect(obj.x).toBeUndefined();
|
|
expect(obj.y).toBe(2);
|
|
});
|
|
|
|
test("delete then re-add", () => {
|
|
const obj = { x: 1 };
|
|
delete obj.x;
|
|
obj.x = 2;
|
|
expect(obj.x).toBe(2);
|
|
});
|
|
|
|
test("delete exposes prototype property", () => {
|
|
const proto = { x: "proto" };
|
|
const obj = Object.create(proto);
|
|
obj.x = "own";
|
|
expect(obj.x).toBe("own");
|
|
delete obj.x;
|
|
expect(obj.x).toBe("proto");
|
|
});
|
|
|
|
test("delete in loop affects IC", () => {
|
|
const objects = [];
|
|
for (let i = 0; i < 100; i++) {
|
|
const obj = { x: i, y: i * 2 };
|
|
if (i % 2 === 0) delete obj.x;
|
|
objects.push(obj);
|
|
}
|
|
let undefinedCount = 0;
|
|
for (const obj of objects) {
|
|
if (obj.x === undefined) undefinedCount++;
|
|
}
|
|
expect(undefinedCount).toBe(50);
|
|
});
|
|
});
|
|
|
|
describe("getters and setters", () => {
|
|
test("own getter", () => {
|
|
const obj = {
|
|
_x: 10,
|
|
get x() {
|
|
return this._x * 2;
|
|
},
|
|
};
|
|
expect(obj.x).toBe(20);
|
|
});
|
|
|
|
test("own setter", () => {
|
|
const obj = {
|
|
_x: 0,
|
|
set x(v) {
|
|
this._x = v * 2;
|
|
},
|
|
};
|
|
obj.x = 5;
|
|
expect(obj._x).toBe(10);
|
|
});
|
|
|
|
test("prototype getter", () => {
|
|
const proto = {
|
|
get value() {
|
|
return 42;
|
|
},
|
|
};
|
|
const obj = Object.create(proto);
|
|
expect(obj.value).toBe(42);
|
|
});
|
|
|
|
test("prototype setter", () => {
|
|
const proto = {
|
|
_value: 0,
|
|
set value(v) {
|
|
this._value = v;
|
|
},
|
|
};
|
|
const obj = Object.create(proto);
|
|
obj.value = 100;
|
|
expect(obj._value).toBe(100);
|
|
expect(proto._value).toBe(0);
|
|
});
|
|
|
|
test("getter in loop", () => {
|
|
const obj = {
|
|
_counter: 0,
|
|
get counter() {
|
|
return this._counter++;
|
|
},
|
|
};
|
|
let sum = 0;
|
|
for (let i = 0; i < 100; i++) {
|
|
sum += obj.counter;
|
|
}
|
|
expect(sum).toBe(4950);
|
|
});
|
|
|
|
test("setter in loop", () => {
|
|
const obj = {
|
|
_value: 0,
|
|
set value(v) {
|
|
this._value += v;
|
|
},
|
|
};
|
|
for (let i = 0; i < 100; i++) {
|
|
obj.value = 1;
|
|
}
|
|
expect(obj._value).toBe(100);
|
|
});
|
|
|
|
test("shadowing getter with data property", () => {
|
|
const proto = {
|
|
get x() {
|
|
return "getter";
|
|
},
|
|
};
|
|
const obj = Object.create(proto);
|
|
expect(obj.x).toBe("getter");
|
|
Object.defineProperty(obj, "x", { value: "data", writable: true });
|
|
expect(obj.x).toBe("data");
|
|
});
|
|
|
|
test("getter returning different values based on state", () => {
|
|
const obj = {
|
|
state: 0,
|
|
get dynamic() {
|
|
return this.state++;
|
|
},
|
|
};
|
|
const results = [];
|
|
for (let i = 0; i < 10; i++) {
|
|
results.push(obj.dynamic);
|
|
}
|
|
expect(results).toEqual([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]);
|
|
});
|
|
});
|
|
|
|
describe("Object.defineProperty", () => {
|
|
test("defineProperty non-writable", () => {
|
|
const obj = {};
|
|
Object.defineProperty(obj, "x", { value: 1, writable: false });
|
|
expect(obj.x).toBe(1);
|
|
obj.x = 2;
|
|
expect(obj.x).toBe(1);
|
|
});
|
|
|
|
test("defineProperty non-configurable", () => {
|
|
const obj = {};
|
|
Object.defineProperty(obj, "x", { value: 1, configurable: false });
|
|
expect(obj.x).toBe(1);
|
|
expect(() => {
|
|
Object.defineProperty(obj, "x", { value: 2 });
|
|
}).toThrow(TypeError);
|
|
});
|
|
|
|
test("defineProperty on prototype affects lookup", () => {
|
|
const proto = {};
|
|
const obj = Object.create(proto);
|
|
expect(obj.x).toBeUndefined();
|
|
Object.defineProperty(proto, "x", { value: 42 });
|
|
expect(obj.x).toBe(42);
|
|
});
|
|
|
|
test("convert data to accessor", () => {
|
|
const obj = { x: 1 };
|
|
expect(obj.x).toBe(1);
|
|
Object.defineProperty(obj, "x", {
|
|
get() {
|
|
return 100;
|
|
},
|
|
configurable: true,
|
|
});
|
|
expect(obj.x).toBe(100);
|
|
});
|
|
|
|
test("convert accessor to data", () => {
|
|
const obj = {
|
|
get x() {
|
|
return 1;
|
|
},
|
|
};
|
|
expect(obj.x).toBe(1);
|
|
Object.defineProperty(obj, "x", { value: 100, configurable: true, writable: true });
|
|
expect(obj.x).toBe(100);
|
|
});
|
|
});
|
|
|
|
describe("prototype manipulation", () => {
|
|
test("changing __proto__ at runtime", () => {
|
|
const proto1 = { x: 1 };
|
|
const proto2 = { x: 2 };
|
|
const obj = Object.create(proto1);
|
|
expect(obj.x).toBe(1);
|
|
Object.setPrototypeOf(obj, proto2);
|
|
expect(obj.x).toBe(2);
|
|
});
|
|
|
|
test("changing __proto__ in loop", () => {
|
|
const proto1 = { value: 1 };
|
|
const proto2 = { value: 2 };
|
|
const obj = Object.create(proto1);
|
|
|
|
let sum = 0;
|
|
for (let i = 0; i < 100; i++) {
|
|
if (i === 50) Object.setPrototypeOf(obj, proto2);
|
|
sum += obj.value;
|
|
}
|
|
expect(sum).toBe(150);
|
|
});
|
|
|
|
test("null prototype then add new prototype", () => {
|
|
const obj = Object.create(null);
|
|
obj.x = 1;
|
|
expect(obj.x).toBe(1);
|
|
expect(obj.toString).toBeUndefined();
|
|
|
|
Object.setPrototypeOf(obj, {
|
|
y: 2,
|
|
toString() {
|
|
return "obj";
|
|
},
|
|
});
|
|
expect(obj.x).toBe(1);
|
|
expect(obj.y).toBe(2);
|
|
expect(obj.toString()).toBe("obj");
|
|
});
|
|
|
|
test("cyclic prototype chain attempt", () => {
|
|
const a = {};
|
|
const b = Object.create(a);
|
|
expect(() => {
|
|
Object.setPrototypeOf(a, b);
|
|
}).toThrow(TypeError);
|
|
});
|
|
});
|
|
|
|
describe("proxy objects", () => {
|
|
test("proxy get trap", () => {
|
|
const target = { x: 1 };
|
|
const proxy = new Proxy(target, {
|
|
get(t, prop) {
|
|
return t[prop] * 2;
|
|
},
|
|
});
|
|
expect(proxy.x).toBe(2);
|
|
});
|
|
|
|
test("proxy set trap", () => {
|
|
const target = { x: 1 };
|
|
const proxy = new Proxy(target, {
|
|
set(t, prop, value) {
|
|
t[prop] = value * 2;
|
|
return true;
|
|
},
|
|
});
|
|
proxy.x = 5;
|
|
expect(target.x).toBe(10);
|
|
});
|
|
|
|
test("proxy in prototype chain", () => {
|
|
const protoTarget = { inherited: 42 };
|
|
const protoProxy = new Proxy(protoTarget, {
|
|
get(t, prop) {
|
|
if (prop === "inherited") return t[prop] + 1;
|
|
return t[prop];
|
|
},
|
|
});
|
|
const obj = Object.create(protoProxy);
|
|
obj.own = 1;
|
|
expect(obj.inherited).toBe(43);
|
|
expect(obj.own).toBe(1);
|
|
});
|
|
|
|
test("proxy get trap in loop", () => {
|
|
let getCount = 0;
|
|
const proxy = new Proxy(
|
|
{ x: 10 },
|
|
{
|
|
get(t, prop) {
|
|
getCount++;
|
|
return t[prop];
|
|
},
|
|
}
|
|
);
|
|
let sum = 0;
|
|
for (let i = 0; i < 100; i++) {
|
|
sum += proxy.x;
|
|
}
|
|
expect(sum).toBe(1000);
|
|
expect(getCount).toBe(100);
|
|
});
|
|
|
|
test("proxy that returns different values", () => {
|
|
let counter = 0;
|
|
const proxy = new Proxy(
|
|
{},
|
|
{
|
|
get(t, prop) {
|
|
return counter++;
|
|
},
|
|
}
|
|
);
|
|
const results = [];
|
|
for (let i = 0; i < 5; i++) {
|
|
results.push(proxy.x);
|
|
}
|
|
expect(results).toEqual([0, 1, 2, 3, 4]);
|
|
});
|
|
});
|
|
|
|
describe("special object types", () => {
|
|
test("array length property", () => {
|
|
const arr = [1, 2, 3];
|
|
expect(arr.length).toBe(3);
|
|
arr.length = 2;
|
|
expect(arr.length).toBe(2);
|
|
expect(arr[2]).toBeUndefined();
|
|
});
|
|
|
|
test("array index access", () => {
|
|
const arr = [10, 20, 30];
|
|
let sum = 0;
|
|
for (let i = 0; i < 1000; i++) {
|
|
sum += arr[i % 3];
|
|
}
|
|
expect(sum).toBe(19990);
|
|
});
|
|
|
|
test("array with holes", () => {
|
|
const arr = [1, , 3];
|
|
expect(arr[0]).toBe(1);
|
|
expect(arr[1]).toBeUndefined();
|
|
expect(arr[2]).toBe(3);
|
|
expect(1 in arr).toBeFalse();
|
|
});
|
|
|
|
test("array prototype method access", () => {
|
|
const arr = [1, 2, 3];
|
|
let sum = 0;
|
|
for (let i = 0; i < 100; i++) {
|
|
sum += arr.reduce((a, b) => a + b, 0);
|
|
}
|
|
expect(sum).toBe(600);
|
|
});
|
|
|
|
test("typed array property access", () => {
|
|
const arr = new Int32Array([1, 2, 3, 4, 5]);
|
|
let sum = 0;
|
|
for (let i = 0; i < 1000; i++) {
|
|
sum += arr[i % 5];
|
|
}
|
|
expect(sum).toBe(3000);
|
|
});
|
|
|
|
test("string index access", () => {
|
|
const str = "hello";
|
|
let result = "";
|
|
for (let i = 0; i < str.length; i++) {
|
|
result += str[i];
|
|
}
|
|
expect(result).toBe("hello");
|
|
});
|
|
|
|
test("string length property", () => {
|
|
const str = "hello";
|
|
expect(str.length).toBe(5);
|
|
});
|
|
|
|
test("function properties", () => {
|
|
function foo() {}
|
|
foo.custom = 42;
|
|
expect(foo.custom).toBe(42);
|
|
expect(foo.name).toBe("foo");
|
|
expect(typeof foo.prototype).toBe("object");
|
|
});
|
|
|
|
test("arguments object", () => {
|
|
function test() {
|
|
expect(arguments[0]).toBe(1);
|
|
expect(arguments[1]).toBe(2);
|
|
expect(arguments.length).toBe(3);
|
|
}
|
|
test(1, 2, 3);
|
|
});
|
|
});
|
|
|
|
describe("symbol properties", () => {
|
|
test("symbol property access", () => {
|
|
const sym = Symbol("test");
|
|
const obj = { [sym]: 42 };
|
|
expect(obj[sym]).toBe(42);
|
|
});
|
|
|
|
test("symbol in prototype", () => {
|
|
const sym = Symbol("inherited");
|
|
const proto = { [sym]: "proto" };
|
|
const obj = Object.create(proto);
|
|
expect(obj[sym]).toBe("proto");
|
|
});
|
|
|
|
test("well-known symbols", () => {
|
|
const obj = {
|
|
[Symbol.toStringTag]: "Custom",
|
|
};
|
|
expect(Object.prototype.toString.call(obj)).toBe("[object Custom]");
|
|
});
|
|
|
|
test("symbol property iteration", () => {
|
|
const sym1 = Symbol("a");
|
|
const sym2 = Symbol("b");
|
|
const obj = { [sym1]: 1, [sym2]: 2, regular: 3 };
|
|
|
|
const symbols = Object.getOwnPropertySymbols(obj);
|
|
expect(symbols.length).toBe(2);
|
|
expect(obj[symbols[0]]).toBe(1);
|
|
expect(obj[symbols[1]]).toBe(2);
|
|
});
|
|
});
|
|
|
|
describe("computed property names", () => {
|
|
test("computed property get", () => {
|
|
const obj = { a: 1, b: 2, c: 3 };
|
|
const keys = ["a", "b", "c"];
|
|
let sum = 0;
|
|
for (const key of keys) {
|
|
sum += obj[key];
|
|
}
|
|
expect(sum).toBe(6);
|
|
});
|
|
|
|
test("computed property set", () => {
|
|
const obj = {};
|
|
const keys = ["a", "b", "c"];
|
|
for (let i = 0; i < keys.length; i++) {
|
|
obj[keys[i]] = i;
|
|
}
|
|
expect(obj.a).toBe(0);
|
|
expect(obj.b).toBe(1);
|
|
expect(obj.c).toBe(2);
|
|
});
|
|
|
|
test("computed property with numbers", () => {
|
|
const obj = {};
|
|
for (let i = 0; i < 100; i++) {
|
|
obj[i] = i * 2;
|
|
}
|
|
let sum = 0;
|
|
for (let i = 0; i < 100; i++) {
|
|
sum += obj[i];
|
|
}
|
|
expect(sum).toBe(9900);
|
|
});
|
|
|
|
test("computed property coercion", () => {
|
|
const obj = {};
|
|
obj[1] = "one";
|
|
obj["1"] = "one-string";
|
|
expect(obj[1]).toBe("one-string");
|
|
expect(obj["1"]).toBe("one-string");
|
|
});
|
|
});
|
|
|
|
describe("constructor patterns", () => {
|
|
test("constructor instance properties", () => {
|
|
function Point(x, y) {
|
|
this.x = x;
|
|
this.y = y;
|
|
}
|
|
const points = [];
|
|
for (let i = 0; i < 100; i++) {
|
|
points.push(new Point(i, i * 2));
|
|
}
|
|
let sumX = 0,
|
|
sumY = 0;
|
|
for (const p of points) {
|
|
sumX += p.x;
|
|
sumY += p.y;
|
|
}
|
|
expect(sumX).toBe(4950);
|
|
expect(sumY).toBe(9900);
|
|
});
|
|
|
|
test("constructor prototype methods", () => {
|
|
function Counter() {
|
|
this.value = 0;
|
|
}
|
|
Counter.prototype.increment = function () {
|
|
this.value++;
|
|
};
|
|
Counter.prototype.get = function () {
|
|
return this.value;
|
|
};
|
|
|
|
const c = new Counter();
|
|
for (let i = 0; i < 100; i++) {
|
|
c.increment();
|
|
}
|
|
expect(c.get()).toBe(100);
|
|
});
|
|
|
|
test("class instance properties", () => {
|
|
class Point {
|
|
constructor(x, y) {
|
|
this.x = x;
|
|
this.y = y;
|
|
}
|
|
}
|
|
const points = [];
|
|
for (let i = 0; i < 100; i++) {
|
|
points.push(new Point(i, i * 2));
|
|
}
|
|
let sumX = 0,
|
|
sumY = 0;
|
|
for (const p of points) {
|
|
sumX += p.x;
|
|
sumY += p.y;
|
|
}
|
|
expect(sumX).toBe(4950);
|
|
expect(sumY).toBe(9900);
|
|
});
|
|
|
|
test("class with getters/setters", () => {
|
|
class Box {
|
|
constructor(v) {
|
|
this._v = v;
|
|
}
|
|
get value() {
|
|
return this._v * 2;
|
|
}
|
|
set value(v) {
|
|
this._v = v / 2;
|
|
}
|
|
}
|
|
const b = new Box(10);
|
|
expect(b.value).toBe(20);
|
|
b.value = 100;
|
|
expect(b._v).toBe(50);
|
|
expect(b.value).toBe(100);
|
|
});
|
|
|
|
test("class inheritance", () => {
|
|
class Animal {
|
|
constructor(name) {
|
|
this.name = name;
|
|
}
|
|
speak() {
|
|
return "...";
|
|
}
|
|
}
|
|
class Dog extends Animal {
|
|
speak() {
|
|
return "woof";
|
|
}
|
|
}
|
|
class Cat extends Animal {
|
|
speak() {
|
|
return "meow";
|
|
}
|
|
}
|
|
|
|
const animals = [new Dog("Rex"), new Cat("Whiskers"), new Dog("Buddy")];
|
|
const sounds = animals.map(a => a.speak());
|
|
expect(sounds).toEqual(["woof", "meow", "woof"]);
|
|
});
|
|
|
|
test("class static properties", () => {
|
|
class Counter {
|
|
static count = 0;
|
|
constructor() {
|
|
Counter.count++;
|
|
}
|
|
}
|
|
for (let i = 0; i < 10; i++) {
|
|
new Counter();
|
|
}
|
|
expect(Counter.count).toBe(10);
|
|
});
|
|
});
|
|
|
|
describe("edge cases", () => {
|
|
test("property named __proto__", () => {
|
|
const obj = Object.create(null);
|
|
obj.__proto__ = 42;
|
|
expect(obj.__proto__).toBe(42);
|
|
});
|
|
|
|
test("property named constructor", () => {
|
|
const obj = { constructor: 42 };
|
|
expect(obj.constructor).toBe(42);
|
|
});
|
|
|
|
test("numeric string keys vs number keys", () => {
|
|
const obj = {};
|
|
obj["0"] = "string";
|
|
expect(obj[0]).toBe("string");
|
|
obj[0] = "number";
|
|
expect(obj["0"]).toBe("number");
|
|
});
|
|
|
|
test("very long property name", () => {
|
|
const longName = "a".repeat(1000);
|
|
const obj = { [longName]: 42 };
|
|
expect(obj[longName]).toBe(42);
|
|
});
|
|
|
|
test("property access on primitives", () => {
|
|
expect((42).toString()).toBe("42");
|
|
expect("hello".toUpperCase()).toBe("HELLO");
|
|
expect(true.toString()).toBe("true");
|
|
});
|
|
|
|
test("undefined/null property access", () => {
|
|
expect(() => null.x).toThrow(TypeError);
|
|
expect(() => undefined.x).toThrow(TypeError);
|
|
});
|
|
|
|
test("hasOwnProperty vs in operator", () => {
|
|
const proto = { inherited: 1 };
|
|
const obj = Object.create(proto);
|
|
obj.own = 2;
|
|
|
|
expect("own" in obj).toBeTrue();
|
|
expect("inherited" in obj).toBeTrue();
|
|
expect(obj.hasOwnProperty("own")).toBeTrue();
|
|
expect(obj.hasOwnProperty("inherited")).toBeFalse();
|
|
});
|
|
|
|
test("Object.keys vs Object.getOwnPropertyNames", () => {
|
|
const obj = {};
|
|
Object.defineProperty(obj, "hidden", { value: 1, enumerable: false });
|
|
obj.visible = 2;
|
|
|
|
expect(Object.keys(obj)).toEqual(["visible"]);
|
|
expect(Object.getOwnPropertyNames(obj).sort()).toEqual(["hidden", "visible"]);
|
|
});
|
|
|
|
test("frozen object property access", () => {
|
|
const obj = Object.freeze({ x: 1, y: 2 });
|
|
expect(obj.x).toBe(1);
|
|
obj.x = 100;
|
|
expect(obj.x).toBe(1);
|
|
obj.z = 3;
|
|
expect(obj.z).toBeUndefined();
|
|
});
|
|
|
|
test("sealed object property access", () => {
|
|
const obj = Object.seal({ x: 1 });
|
|
expect(obj.x).toBe(1);
|
|
obj.x = 100;
|
|
expect(obj.x).toBe(100);
|
|
obj.y = 2;
|
|
expect(obj.y).toBeUndefined();
|
|
});
|
|
|
|
test("non-extensible object", () => {
|
|
const obj = Object.preventExtensions({ x: 1 });
|
|
obj.x = 2;
|
|
expect(obj.x).toBe(2);
|
|
obj.y = 3;
|
|
expect(obj.y).toBeUndefined();
|
|
});
|
|
});
|
|
|
|
describe("prototype mutations during access", () => {
|
|
test("modify prototype during iteration", () => {
|
|
const proto = { a: 1, b: 2 };
|
|
const obj = Object.create(proto);
|
|
|
|
const values = [];
|
|
for (const key in obj) {
|
|
values.push(obj[key]);
|
|
if (key === "a") proto.c = 3;
|
|
}
|
|
expect(obj.c).toBe(3);
|
|
});
|
|
|
|
test("delete prototype property during access", () => {
|
|
const proto = { x: 1 };
|
|
const obj = Object.create(proto);
|
|
|
|
expect(obj.x).toBe(1);
|
|
delete proto.x;
|
|
expect(obj.x).toBeUndefined();
|
|
});
|
|
|
|
test("add own property shadows prototype during loop", () => {
|
|
const proto = { value: "proto" };
|
|
const objects = [];
|
|
for (let i = 0; i < 10; i++) {
|
|
objects.push(Object.create(proto));
|
|
}
|
|
|
|
const results = [];
|
|
for (let i = 0; i < objects.length; i++) {
|
|
results.push(objects[i].value);
|
|
if (i === 4) objects[5].value = "own";
|
|
}
|
|
expect(results).toEqual([
|
|
"proto",
|
|
"proto",
|
|
"proto",
|
|
"proto",
|
|
"proto",
|
|
"own",
|
|
"proto",
|
|
"proto",
|
|
"proto",
|
|
"proto",
|
|
]);
|
|
});
|
|
});
|
|
|
|
describe("mixed access patterns", () => {
|
|
test("alternating get and set", () => {
|
|
const obj = { x: 0 };
|
|
for (let i = 0; i < 100; i++) {
|
|
obj.x = obj.x + 1;
|
|
}
|
|
expect(obj.x).toBe(100);
|
|
});
|
|
|
|
test("multiple objects same shape alternating access", () => {
|
|
const a = { x: 1, y: 2 };
|
|
const b = { x: 10, y: 20 };
|
|
let sum = 0;
|
|
for (let i = 0; i < 100; i++) {
|
|
sum += i % 2 === 0 ? a.x + a.y : b.x + b.y;
|
|
}
|
|
expect(sum).toBe(1650);
|
|
});
|
|
|
|
test("property access through variable", () => {
|
|
const obj = { a: 1, b: 2, c: 3 };
|
|
const props = ["a", "b", "c", "a", "b", "c"];
|
|
let sum = 0;
|
|
for (const p of props) {
|
|
sum += obj[p];
|
|
}
|
|
expect(sum).toBe(12);
|
|
});
|
|
|
|
test("nested object access", () => {
|
|
const obj = {
|
|
level1: {
|
|
level2: {
|
|
level3: {
|
|
value: 42,
|
|
},
|
|
},
|
|
},
|
|
};
|
|
let sum = 0;
|
|
for (let i = 0; i < 100; i++) {
|
|
sum += obj.level1.level2.level3.value;
|
|
}
|
|
expect(sum).toBe(4200);
|
|
});
|
|
|
|
test("optional chaining", () => {
|
|
const obj = { a: { b: { c: 42 } } };
|
|
expect(obj?.a?.b?.c).toBe(42);
|
|
expect(obj?.x?.y?.z).toBeUndefined();
|
|
expect(null?.x).toBeUndefined();
|
|
});
|
|
});
|
|
|
|
describe("IC invalidation scenarios", () => {
|
|
test("IC invalidation by adding property to prototype", () => {
|
|
const proto = {};
|
|
const objects = [];
|
|
for (let i = 0; i < 10; i++) {
|
|
objects.push(Object.create(proto));
|
|
}
|
|
|
|
for (let i = 0; i < 100; i++) {
|
|
for (const obj of objects) {
|
|
obj.x;
|
|
}
|
|
}
|
|
|
|
proto.x = 42;
|
|
|
|
let sum = 0;
|
|
for (const obj of objects) {
|
|
sum += obj.x;
|
|
}
|
|
expect(sum).toBe(420);
|
|
});
|
|
|
|
test("IC invalidation by deleting property from prototype", () => {
|
|
const proto = { x: 42 };
|
|
const obj = Object.create(proto);
|
|
|
|
let sum = 0;
|
|
for (let i = 0; i < 100; i++) {
|
|
sum += obj.x;
|
|
}
|
|
expect(sum).toBe(4200);
|
|
|
|
delete proto.x;
|
|
|
|
expect(obj.x).toBeUndefined();
|
|
});
|
|
|
|
test("IC with changing object shapes", () => {
|
|
function getValue(obj) {
|
|
return obj.value;
|
|
}
|
|
|
|
const obj1 = { value: 1 };
|
|
const obj2 = { a: 0, value: 2 };
|
|
const obj3 = { a: 0, b: 0, value: 3 };
|
|
|
|
expect(getValue(obj1)).toBe(1);
|
|
expect(getValue(obj2)).toBe(2);
|
|
expect(getValue(obj3)).toBe(3);
|
|
expect(getValue(obj1)).toBe(1);
|
|
expect(getValue(obj2)).toBe(2);
|
|
});
|
|
});
|
|
|
|
describe("global object access", () => {
|
|
test("global property access", () => {
|
|
globalThis.testGlobal = 42;
|
|
expect(testGlobal).toBe(42);
|
|
delete globalThis.testGlobal;
|
|
});
|
|
|
|
test("global property in loop", () => {
|
|
globalThis.loopCounter = 0;
|
|
for (let i = 0; i < 100; i++) {
|
|
loopCounter++;
|
|
}
|
|
expect(loopCounter).toBe(100);
|
|
delete globalThis.loopCounter;
|
|
});
|
|
});
|
|
|
|
describe("Reflect API", () => {
|
|
test("Reflect.get", () => {
|
|
const obj = { x: 42 };
|
|
expect(Reflect.get(obj, "x")).toBe(42);
|
|
});
|
|
|
|
test("Reflect.set", () => {
|
|
const obj = { x: 1 };
|
|
Reflect.set(obj, "x", 42);
|
|
expect(obj.x).toBe(42);
|
|
});
|
|
|
|
test("Reflect.get with receiver", () => {
|
|
const proto = {
|
|
get x() {
|
|
return this.y * 2;
|
|
},
|
|
};
|
|
const obj = Object.create(proto);
|
|
obj.y = 10;
|
|
expect(Reflect.get(proto, "x", obj)).toBe(20);
|
|
});
|
|
|
|
test("Reflect.has", () => {
|
|
const proto = { inherited: 1 };
|
|
const obj = Object.create(proto);
|
|
obj.own = 2;
|
|
expect(Reflect.has(obj, "own")).toBeTrue();
|
|
expect(Reflect.has(obj, "inherited")).toBeTrue();
|
|
expect(Reflect.has(obj, "missing")).toBeFalse();
|
|
});
|
|
});
|
|
|
|
describe("Map/WeakMap", () => {
|
|
test("Map property-like access pattern", () => {
|
|
const map = new Map();
|
|
map.set("x", 1);
|
|
map.set("y", 2);
|
|
|
|
let sum = 0;
|
|
for (let i = 0; i < 100; i++) {
|
|
sum += map.get("x") + map.get("y");
|
|
}
|
|
expect(sum).toBe(300);
|
|
});
|
|
|
|
test("WeakMap with object keys", () => {
|
|
const wm = new WeakMap();
|
|
const keys = [];
|
|
for (let i = 0; i < 10; i++) {
|
|
const key = {};
|
|
keys.push(key);
|
|
wm.set(key, i);
|
|
}
|
|
|
|
let sum = 0;
|
|
for (const key of keys) {
|
|
sum += wm.get(key);
|
|
}
|
|
expect(sum).toBe(45);
|
|
});
|
|
});
|
|
|
|
describe("super property access", () => {
|
|
test("super property get", () => {
|
|
class Parent {
|
|
getValue() {
|
|
return 42;
|
|
}
|
|
}
|
|
class Child extends Parent {
|
|
getValue() {
|
|
return super.getValue() * 2;
|
|
}
|
|
}
|
|
const c = new Child();
|
|
expect(c.getValue()).toBe(84);
|
|
});
|
|
|
|
test("super property get in loop", () => {
|
|
class Parent {
|
|
getValue() {
|
|
return 1;
|
|
}
|
|
}
|
|
class Child extends Parent {
|
|
getValue() {
|
|
return super.getValue() + 1;
|
|
}
|
|
}
|
|
const c = new Child();
|
|
let sum = 0;
|
|
for (let i = 0; i < 100; i++) {
|
|
sum += c.getValue();
|
|
}
|
|
expect(sum).toBe(200);
|
|
});
|
|
|
|
test("super with getter", () => {
|
|
class Parent {
|
|
get value() {
|
|
return 10;
|
|
}
|
|
}
|
|
class Child extends Parent {
|
|
get value() {
|
|
return super.value * 2;
|
|
}
|
|
}
|
|
const c = new Child();
|
|
expect(c.value).toBe(20);
|
|
});
|
|
});
|
|
|
|
describe("polymorphic IC boundary tests", () => {
|
|
test("monomorphic IC (1 shape)", () => {
|
|
function getValue(obj) {
|
|
return obj.x;
|
|
}
|
|
const objects = [];
|
|
for (let i = 0; i < 100; i++) {
|
|
objects.push({ x: i });
|
|
}
|
|
let sum = 0;
|
|
for (const obj of objects) {
|
|
sum += getValue(obj);
|
|
}
|
|
expect(sum).toBe(4950);
|
|
});
|
|
|
|
test("dimorphic IC (2 shapes)", () => {
|
|
function getValue(obj) {
|
|
return obj.x;
|
|
}
|
|
const shape1 = [];
|
|
const shape2 = [];
|
|
for (let i = 0; i < 50; i++) {
|
|
shape1.push({ x: i });
|
|
shape2.push({ x: i, y: 0 });
|
|
}
|
|
let sum = 0;
|
|
for (let i = 0; i < 50; i++) {
|
|
sum += getValue(shape1[i]);
|
|
sum += getValue(shape2[i]);
|
|
}
|
|
expect(sum).toBe(2450);
|
|
});
|
|
|
|
test("trimorphic IC (3 shapes)", () => {
|
|
function getValue(obj) {
|
|
return obj.x;
|
|
}
|
|
const objects = [];
|
|
for (let i = 0; i < 99; i++) {
|
|
if (i % 3 === 0) objects.push({ x: i });
|
|
else if (i % 3 === 1) objects.push({ x: i, y: 0 });
|
|
else objects.push({ x: i, y: 0, z: 0 });
|
|
}
|
|
let sum = 0;
|
|
for (const obj of objects) {
|
|
sum += getValue(obj);
|
|
}
|
|
expect(sum).toBe(4851);
|
|
});
|
|
|
|
test("tetramorphic IC (4 shapes - at limit)", () => {
|
|
function getValue(obj) {
|
|
return obj.x;
|
|
}
|
|
const objects = [];
|
|
for (let i = 0; i < 100; i++) {
|
|
if (i % 4 === 0) objects.push({ x: i });
|
|
else if (i % 4 === 1) objects.push({ x: i, a: 0 });
|
|
else if (i % 4 === 2) objects.push({ x: i, b: 0 });
|
|
else objects.push({ x: i, c: 0 });
|
|
}
|
|
let sum = 0;
|
|
for (const obj of objects) {
|
|
sum += getValue(obj);
|
|
}
|
|
expect(sum).toBe(4950);
|
|
});
|
|
|
|
test("pentamorphic IC (5 shapes - exceeds limit)", () => {
|
|
function getValue(obj) {
|
|
return obj.x;
|
|
}
|
|
const objects = [];
|
|
for (let i = 0; i < 100; i++) {
|
|
if (i % 5 === 0) objects.push({ x: i });
|
|
else if (i % 5 === 1) objects.push({ x: i, a: 0 });
|
|
else if (i % 5 === 2) objects.push({ x: i, b: 0 });
|
|
else if (i % 5 === 3) objects.push({ x: i, c: 0 });
|
|
else objects.push({ x: i, d: 0 });
|
|
}
|
|
let sum = 0;
|
|
for (const obj of objects) {
|
|
sum += getValue(obj);
|
|
}
|
|
expect(sum).toBe(4950);
|
|
});
|
|
|
|
test("megamorphic IC (20 shapes)", () => {
|
|
function getValue(obj) {
|
|
return obj.x;
|
|
}
|
|
const objects = [];
|
|
for (let i = 0; i < 100; i++) {
|
|
const obj = { x: i };
|
|
for (let j = 0; j < i % 20; j++) {
|
|
obj["prop" + j] = j;
|
|
}
|
|
objects.push(obj);
|
|
}
|
|
let sum = 0;
|
|
for (const obj of objects) {
|
|
sum += getValue(obj);
|
|
}
|
|
expect(sum).toBe(4950);
|
|
});
|
|
|
|
test("polymorphic IC with prototype differences", () => {
|
|
function getValue(obj) {
|
|
return obj.x;
|
|
}
|
|
const proto1 = { type: 1 };
|
|
const proto2 = { type: 2 };
|
|
const proto3 = { type: 3 };
|
|
const proto4 = { type: 4 };
|
|
|
|
const objects = [];
|
|
for (let i = 0; i < 100; i++) {
|
|
let obj;
|
|
if (i % 4 === 0) obj = Object.create(proto1);
|
|
else if (i % 4 === 1) obj = Object.create(proto2);
|
|
else if (i % 4 === 2) obj = Object.create(proto3);
|
|
else obj = Object.create(proto4);
|
|
obj.x = i;
|
|
objects.push(obj);
|
|
}
|
|
let sum = 0;
|
|
for (const obj of objects) {
|
|
sum += getValue(obj);
|
|
}
|
|
expect(sum).toBe(4950);
|
|
});
|
|
|
|
test("polymorphic setter IC (4 shapes)", () => {
|
|
function setValue(obj, v) {
|
|
obj.x = v;
|
|
}
|
|
const objects = [];
|
|
for (let i = 0; i < 100; i++) {
|
|
if (i % 4 === 0) objects.push({});
|
|
else if (i % 4 === 1) objects.push({ a: 0 });
|
|
else if (i % 4 === 2) objects.push({ b: 0 });
|
|
else objects.push({ c: 0 });
|
|
}
|
|
for (let i = 0; i < objects.length; i++) {
|
|
setValue(objects[i], i);
|
|
}
|
|
let sum = 0;
|
|
for (const obj of objects) {
|
|
sum += obj.x;
|
|
}
|
|
expect(sum).toBe(4950);
|
|
});
|
|
|
|
test("polymorphic IC interleaved access patterns", () => {
|
|
function getValue(obj) {
|
|
return obj.value;
|
|
}
|
|
|
|
const a = { value: 1 };
|
|
const b = { value: 2, extra1: 0 };
|
|
const c = { value: 3, extra1: 0, extra2: 0 };
|
|
const d = { value: 4, extra1: 0, extra2: 0, extra3: 0 };
|
|
|
|
let sum = 0;
|
|
for (let i = 0; i < 100; i++) {
|
|
sum += getValue(a);
|
|
sum += getValue(b);
|
|
sum += getValue(c);
|
|
sum += getValue(d);
|
|
}
|
|
expect(sum).toBe(1000);
|
|
|
|
for (let i = 0; i < 100; i++) {
|
|
sum += getValue(d);
|
|
sum += getValue(c);
|
|
sum += getValue(b);
|
|
sum += getValue(a);
|
|
}
|
|
expect(sum).toBe(2000);
|
|
});
|
|
|
|
test("polymorphic IC with shape transition during access", () => {
|
|
function getValue(obj) {
|
|
return obj.x;
|
|
}
|
|
|
|
const objects = [];
|
|
for (let i = 0; i < 100; i++) {
|
|
objects.push({ x: i });
|
|
}
|
|
|
|
let sum = 0;
|
|
for (const obj of objects) {
|
|
sum += getValue(obj);
|
|
}
|
|
expect(sum).toBe(4950);
|
|
|
|
for (let i = 0; i < 25; i++) {
|
|
objects[i].a = 0;
|
|
}
|
|
for (let i = 25; i < 50; i++) {
|
|
objects[i].b = 0;
|
|
}
|
|
for (let i = 50; i < 75; i++) {
|
|
objects[i].c = 0;
|
|
}
|
|
|
|
sum = 0;
|
|
for (const obj of objects) {
|
|
sum += getValue(obj);
|
|
}
|
|
expect(sum).toBe(4950);
|
|
});
|
|
|
|
test("polymorphic IC crossing 4-shape boundary", () => {
|
|
function getValue(obj) {
|
|
return obj.x;
|
|
}
|
|
|
|
const objects = [{ x: 1 }, { x: 2, a: 0 }, { x: 3, b: 0 }, { x: 4, c: 0 }];
|
|
|
|
for (let i = 0; i < 100; i++) {
|
|
for (const obj of objects) {
|
|
getValue(obj);
|
|
}
|
|
}
|
|
|
|
objects.push({ x: 5, d: 0 });
|
|
|
|
let sum = 0;
|
|
for (let i = 0; i < 100; i++) {
|
|
for (const obj of objects) {
|
|
sum += getValue(obj);
|
|
}
|
|
}
|
|
expect(sum).toBe(1500);
|
|
});
|
|
|
|
test("polymorphic IC with getters mixed in", () => {
|
|
function getValue(obj) {
|
|
return obj.x;
|
|
}
|
|
|
|
const regular1 = { x: 1 };
|
|
const regular2 = { x: 2, y: 0 };
|
|
const withGetter1 = {
|
|
get x() {
|
|
return 3;
|
|
},
|
|
};
|
|
const withGetter2 = {
|
|
get x() {
|
|
return 4;
|
|
},
|
|
y: 0,
|
|
};
|
|
|
|
let sum = 0;
|
|
for (let i = 0; i < 100; i++) {
|
|
sum += getValue(regular1);
|
|
sum += getValue(regular2);
|
|
sum += getValue(withGetter1);
|
|
sum += getValue(withGetter2);
|
|
}
|
|
expect(sum).toBe(1000);
|
|
});
|
|
|
|
test("polymorphic IC with null prototype objects", () => {
|
|
function getValue(obj) {
|
|
return obj.x;
|
|
}
|
|
|
|
const regular = { x: 1 };
|
|
const nullProto = Object.create(null);
|
|
nullProto.x = 2;
|
|
const nullProto2 = Object.create(null);
|
|
nullProto2.x = 3;
|
|
nullProto2.y = 0;
|
|
|
|
let sum = 0;
|
|
for (let i = 0; i < 100; i++) {
|
|
sum += getValue(regular);
|
|
sum += getValue(nullProto);
|
|
sum += getValue(nullProto2);
|
|
}
|
|
expect(sum).toBe(600);
|
|
});
|
|
|
|
test("polymorphic IC stress - rapid shape changes", () => {
|
|
function getValue(obj) {
|
|
return obj.x;
|
|
}
|
|
|
|
let sum = 0;
|
|
for (let i = 0; i < 1000; i++) {
|
|
const obj = { x: i };
|
|
for (let j = 0; j < i % 10; j++) {
|
|
obj["p" + j] = 0;
|
|
}
|
|
sum += getValue(obj);
|
|
}
|
|
expect(sum).toBe(499500);
|
|
});
|
|
|
|
test("polymorphic IC - same property different positions", () => {
|
|
function getValue(obj) {
|
|
return obj.target;
|
|
}
|
|
|
|
const a = { target: 1 };
|
|
const b = { a: 0, target: 2 };
|
|
const c = { a: 0, b: 0, target: 3 };
|
|
const d = { a: 0, b: 0, c: 0, target: 4 };
|
|
|
|
let sum = 0;
|
|
for (let i = 0; i < 100; i++) {
|
|
sum += getValue(a) + getValue(b) + getValue(c) + getValue(d);
|
|
}
|
|
expect(sum).toBe(1000);
|
|
});
|
|
|
|
test("polymorphic IC - property in prototype at different depths", () => {
|
|
function getValue(obj) {
|
|
return obj.inherited;
|
|
}
|
|
|
|
const proto1 = { inherited: 1 };
|
|
const proto2a = Object.create({ inherited: 2 });
|
|
const proto2b = { other: 0 };
|
|
proto2b.inherited = 3;
|
|
|
|
const obj1 = Object.create(proto1);
|
|
const obj2 = Object.create(proto2a);
|
|
const obj3 = Object.create(proto2b);
|
|
const obj4 = { inherited: 4 };
|
|
|
|
let sum = 0;
|
|
for (let i = 0; i < 100; i++) {
|
|
sum += getValue(obj1) + getValue(obj2) + getValue(obj3) + getValue(obj4);
|
|
}
|
|
expect(sum).toBe(1000);
|
|
});
|
|
|
|
test("polymorphic setter IC with shape transitions", () => {
|
|
function setValue(obj, v) {
|
|
obj.x = v;
|
|
}
|
|
|
|
const objects = [];
|
|
for (let i = 0; i < 100; i++) {
|
|
objects.push({});
|
|
}
|
|
|
|
for (let i = 0; i < 100; i++) {
|
|
setValue(objects[i], i);
|
|
}
|
|
|
|
let sum = 0;
|
|
for (const obj of objects) {
|
|
sum += obj.x;
|
|
}
|
|
expect(sum).toBe(4950);
|
|
|
|
for (let i = 0; i < 100; i++) {
|
|
objects[i]["extra" + (i % 4)] = 0;
|
|
}
|
|
|
|
for (let i = 0; i < 100; i++) {
|
|
setValue(objects[i], i * 2);
|
|
}
|
|
|
|
sum = 0;
|
|
for (const obj of objects) {
|
|
sum += obj.x;
|
|
}
|
|
expect(sum).toBe(9900);
|
|
});
|
|
|
|
test("polymorphic IC with frozen/sealed objects", () => {
|
|
function getValue(obj) {
|
|
return obj.x;
|
|
}
|
|
|
|
const regular = { x: 1 };
|
|
const frozen = Object.freeze({ x: 2 });
|
|
const sealed = Object.seal({ x: 3 });
|
|
const nonExtensible = Object.preventExtensions({ x: 4 });
|
|
|
|
let sum = 0;
|
|
for (let i = 0; i < 100; i++) {
|
|
sum += getValue(regular);
|
|
sum += getValue(frozen);
|
|
sum += getValue(sealed);
|
|
sum += getValue(nonExtensible);
|
|
}
|
|
expect(sum).toBe(1000);
|
|
});
|
|
|
|
test("polymorphic IC - array vs object", () => {
|
|
function getFirst(obj) {
|
|
return obj[0];
|
|
}
|
|
|
|
const arr = [42];
|
|
const obj = { 0: 42 };
|
|
const typedArr = new Int32Array([42]);
|
|
|
|
let sum = 0;
|
|
for (let i = 0; i < 100; i++) {
|
|
sum += getFirst(arr);
|
|
sum += getFirst(obj);
|
|
sum += getFirst(typedArr);
|
|
}
|
|
expect(sum).toBe(12600);
|
|
});
|
|
|
|
test("IC cache thrashing - alternating hot/cold", () => {
|
|
function getValue(obj) {
|
|
return obj.x;
|
|
}
|
|
|
|
const shapes = [];
|
|
for (let i = 0; i < 8; i++) {
|
|
const obj = { x: i + 1 };
|
|
for (let j = 0; j < i; j++) {
|
|
obj["prop" + j] = 0;
|
|
}
|
|
shapes.push(obj);
|
|
}
|
|
|
|
let sum = 0;
|
|
for (let i = 0; i < 800; i++) {
|
|
sum += getValue(shapes[i % 8]);
|
|
}
|
|
expect(sum).toBe(3600);
|
|
});
|
|
|
|
test("IC with computed property becoming constant", () => {
|
|
function getValue(obj, prop) {
|
|
return obj[prop];
|
|
}
|
|
|
|
const obj = { a: 1, b: 2, c: 3, d: 4 };
|
|
|
|
let sum = 0;
|
|
const props = ["a", "b", "c", "d"];
|
|
for (let i = 0; i < 100; i++) {
|
|
sum += getValue(obj, props[i % 4]);
|
|
}
|
|
expect(sum).toBe(250);
|
|
|
|
sum = 0;
|
|
for (let i = 0; i < 100; i++) {
|
|
sum += getValue(obj, "a");
|
|
}
|
|
expect(sum).toBe(100);
|
|
});
|
|
});
|