Accessor cache entries can be reused by objects with the same shape even when the accessor functions differ. Reusing a getter cache for a later setter-only object tried to call null instead of returning undefined. Route cached gets through the missing-getter behavior from OrdinaryGet, and fall back to the slow set path when a cached accessor has no setter so strict assignment failures still come from OrdinarySet. Add IC coverage for the missing accessor half in both directions.
1613 lines
42 KiB
JavaScript
1613 lines
42 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);
|
|
});
|
|
|
|
test("cached accessor get with missing getter", () => {
|
|
function getValue(obj) {
|
|
return obj.value;
|
|
}
|
|
|
|
const withGetter = {
|
|
get value() {
|
|
return 1;
|
|
},
|
|
};
|
|
expect(getValue(withGetter)).toBe(1);
|
|
|
|
const withSetter = {
|
|
set value(value) {},
|
|
};
|
|
expect(getValue(withSetter)).toBeUndefined();
|
|
});
|
|
|
|
test("cached accessor set with missing setter", () => {
|
|
function setValue(obj, value) {
|
|
obj.value = value;
|
|
}
|
|
|
|
let setterValue = 0;
|
|
const withSetter = {
|
|
set value(value) {
|
|
setterValue = value;
|
|
},
|
|
};
|
|
setValue(withSetter, 1);
|
|
expect(setterValue).toBe(1);
|
|
|
|
const withGetter = {
|
|
get value() {
|
|
return 2;
|
|
},
|
|
};
|
|
setValue(withGetter, 3);
|
|
expect(withGetter.value).toBe(2);
|
|
});
|
|
});
|
|
|
|
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);
|
|
});
|
|
});
|