Files
ladybird/Tests/LibJS/Runtime/inline-cache-stress.js
2026-01-22 07:46:48 -05:00

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);
});
});