blob: 13b39bdec1e135f956d88afe78b4b452eb1744ec [file] [log] [blame]
// Copyright 2020 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// Flags: --allow-natives-syntax --super-ic --opt
// Flags: --no-always-opt --no-stress-opt --deopt-every-n-times=0
(function TestPropertyIsInTheHomeObjectsProto() {
// Test where the property is a constant found on home object's proto. This
// will generate a minimorphic property load.
class A {}
A.prototype.bar = "wrong value: A.prototype.bar";
class B extends A {};
B.prototype.bar = "correct value";
class C extends B {
foo() { return super.bar; }
}
C.prototype.bar = "wrong value: D.prototype.bar";
%PrepareFunctionForOptimization(C.prototype.foo);
let o = new C();
o.bar = "wrong value: o.bar";
// Fill in the feedback.
let r = o.foo();
assertEquals("correct value", r);
%OptimizeFunctionOnNextCall(C.prototype.foo);
// Test the optimized function.
r = o.foo();
assertEquals("correct value", r);
// Assert that the function was not deoptimized.
assertOptimized(C.prototype.foo);
// Change the property value.
B.prototype.bar = "new value";
r = o.foo();
assertEquals("new value", r);
})();
(function TestPropertyIsGetterInTheHomeObjectsProto() {
// Test where the property is a constant found on home object's proto. This
// will generate a minimorphic property load.
class A {}
A.prototype.bar = "wrong value: A.prototype.bar";
class B extends A {
get bar() { return this.this_value; }
}
class C extends B {
foo() { return super.bar; }
}
C.prototype.bar = "wrong value: D.prototype.bar";
%PrepareFunctionForOptimization(C.prototype.foo);
let o = new C();
o.bar = "wrong value: o.bar";
o.this_value = "correct value";
// Fill in the feedback.
let r = o.foo();
assertEquals("correct value", r);
%OptimizeFunctionOnNextCall(C.prototype.foo);
// Test the optimized function.
r = o.foo();
assertEquals("correct value", r);
// Assert that the function was not deoptimized.
assertOptimized(C.prototype.foo);
// Change the property value.
o.this_value = "new value";
r = o.foo();
assertEquals("new value", r);
})();
(function TestPropertyIsConstantInThePrototypeChain() {
// Test where the property is a constant found on the prototype chain of the
// lookup start object.
class A {}
A.prototype.bar = "wrong value: A.prototype.bar";
class B extends A {};
B.prototype.bar = "correct value";
class C extends B {};
class D extends C {
foo() { return super.bar; }
}
D.prototype.bar = "wrong value: D.prototype.bar";
%PrepareFunctionForOptimization(D.prototype.foo);
let o = new D();
o.bar = "wrong value: o.bar";
// Fill in the feedback.
let r = o.foo();
assertEquals("correct value", r);
%OptimizeFunctionOnNextCall(D.prototype.foo);
// Test the optimized function.
r = o.foo();
assertEquals("correct value", r);
// Assert that the function was not deoptimized.
assertOptimized(D.prototype.foo);
// Change the property value.
B.prototype.bar = "new value";
r = o.foo();
assertEquals("new value", r);
// Assert that the function was deoptimized (dependency to the constant
// value).
assertFalse(isOptimized(D.prototype.foo));
})();
(function TestPropertyIsNonConstantData() {
// Test for a case where the property is a non-constant data property found
// in the lookup start object.
class A {}
A.prototype.bar = "wrong value: A.prototype.bar";
class B extends A {};
B.prototype.bar = "initial value";
class C extends B {
foo() { return super.bar; }
}
C.prototype.bar = "wrong value: C.prototype.bar";
%PrepareFunctionForOptimization(C.prototype.foo);
let o = new C();
o.bar = "wrong value: o.bar";
// Make the property look like a non-constant.
B.prototype.bar = "correct value";
// Fill in the feedback.
let r = o.foo();
assertEquals("correct value", r);
%OptimizeFunctionOnNextCall(C.prototype.foo);
// Test the optimized function.
r = o.foo();
assertEquals("correct value", r);
// Assert that the function was not deoptimized.
assertOptimized(C.prototype.foo);
// Change the property value.
B.prototype.bar = "new value";
r = o.foo();
assertEquals("new value", r);
// Assert that the function was still not deoptimized (the value was not a
// constant to begin with).
assertOptimized(C.prototype.foo);
})();
(function TestPropertyIsGetter() {
class A {}
A.prototype.bar = "wrong value: A.prototype.bar";
class B extends A {
get bar() {
return this.test_value;
}
};
class C extends B {}
class D extends C {
foo() {
const b = super.bar;
return b;
}
}
%PrepareFunctionForOptimization(D.prototype.foo);
D.prototype.bar = "wrong value: D.prototype.bar";
let o = new D();
o.bar = "wrong value: o.bar";
o.test_value = "correct value";
// Fill in the feedback.
let r = o.foo();
assertEquals("correct value", r);
%OptimizeFunctionOnNextCall(D.prototype.foo);
// Test the optimized function.
r = o.foo();
assertEquals("correct value", r);
// Assert that the function was not deoptimized.
assertOptimized(D.prototype.foo);
})();
(function TestPropertyInsertedInTheMiddle() {
// Test for a case where the property is a constant found in the lookup start
// object.
class A {}
A.prototype.bar = "correct value";
class B extends A {};
class C extends B {
foo() { return super.bar; }
}
C.prototype.bar = "wrong value: C.prototype.bar";
%PrepareFunctionForOptimization(C.prototype.foo);
let o = new C();
o.bar = "wrong value: o.bar";
// Fill in the feedback.
let r = o.foo();
assertEquals("correct value", r);
%OptimizeFunctionOnNextCall(C.prototype.foo);
// Test the optimized function.
r = o.foo();
assertEquals("correct value", r);
// Assert that the function was not deoptimized.
assertOptimized(C.prototype.foo);
// Insert the property into the prototype chain between the lookup start
// object and the old holder.
B.prototype.bar = "new value";
r = o.foo();
assertEquals("new value", r);
// Assert that the function was deoptimized (holder changed).
assertFalse(isOptimized(C.prototype.foo));
})();
(function TestUnexpectedHomeObjectPrototypeDeoptimizes() {
class A {}
A.prototype.bar = "wrong value: A.prototype.bar";
class B extends A {}
B.prototype.bar = "correct value";
class C extends B {}
class D extends C {
foo() { return super.bar; }
}
%PrepareFunctionForOptimization(D.prototype.foo);
D.prototype.bar = "wrong value: D.prototype.bar";
const o = new D();
// Fill in the feedback.
let r = o.foo();
assertEquals("correct value", r);
%OptimizeFunctionOnNextCall(D.prototype.foo);
// Test the optimized function.
r = o.foo();
assertEquals("correct value", r);
// Assert that the function was not deoptimized.
assertOptimized(D.prototype.foo);
// Change the home object's prototype.
D.prototype.__proto__ = {"bar": "new value"};
r = o.foo();
assertEquals("new value", r);
// Assert that the function was deoptimized.
assertEquals(false, isOptimized(D.prototype.foo));
})();
(function TestUnexpectedReceiverDoesNotDeoptimize() {
class A {}
A.prototype.bar = "wrong value: A.prototype.bar";
class B extends A {};
B.prototype.bar = "correct value";
class C extends B {
foo() { return super.bar; }
}
C.prototype.bar = "wrong value: C.prototype.bar";
%PrepareFunctionForOptimization(C.prototype.foo);
let o = new C();
o.bar = "wrong value: o.bar";
// Fill in the feedback.
let r = o.foo();
assertEquals("correct value", r);
%OptimizeFunctionOnNextCall(C.prototype.foo);
o.foo();
assertOptimized(C.prototype.foo);
// Test the optimized function with an unexpected receiver.
r = C.prototype.foo.call({'lol': 5});
assertEquals("correct value", r);
// Assert that the function was not deoptimized.
assertOptimized(C.prototype.foo);
})();
(function TestPolymorphic() {
class A {}
A.prototype.bar = "wrong value: A.prototype.bar";
class B extends A {}
B.prototype.bar = "correct value";
class C extends B {}
class D extends C {
foo() { return super.bar; }
}
%PrepareFunctionForOptimization(D.prototype.foo);
D.prototype.bar = "wrong value: D.prototype.bar";
const o = new D();
// Create objects which will act as the "home object's prototype" later.
const prototypes = [{"a": 0}, {"b": 0}];
for (p of prototypes) {
p.__proto__ = B.prototype;
}
// Fill in the feedback (polymorphic).
for (p of prototypes) {
D.prototype.__proto__ = p;
const r = o.foo();
assertEquals("correct value", r);
}
%OptimizeFunctionOnNextCall(D.prototype.foo);
// Test the optimized function - don't change the home object's proto any
// more.
let r = o.foo();
assertEquals("correct value", r);
// Assert that the function was not deoptimized.
assertOptimized(D.prototype.foo);
})();
(function TestPolymorphicWithGetter() {
class A {}
A.prototype.bar = "wrong value: A.prototype.bar";
class B extends A {
get bar() {
return this.test_value;
}
}
class C extends B {}
class D extends C {
foo() { return super.bar; }
}
%PrepareFunctionForOptimization(D.prototype.foo);
D.prototype.bar = "wrong value: D.prototype.bar";
const o = new D();
o.test_value = "correct value";
// Create objects which will act as the "home object's prototype" later.
const prototypes = [{"a": 0}, {"b": 0}];
for (p of prototypes) {
p.__proto__ = B.prototype;
}
// Fill in the feedback.
for (p of prototypes) {
D.prototype.__proto__ = p;
const r = o.foo();
assertEquals("correct value", r);
}
%OptimizeFunctionOnNextCall(D.prototype.foo);
// Test the optimized function - don't change the home object's proto any
// more.
const r = o.foo();
assertEquals("correct value", r);
// Assert that the function was not deoptimized.
assertOptimized(D.prototype.foo);
})();
(function TestPolymorphicMixinDoesNotDeopt() {
function createClasses() {
class A {}
A.prototype.bar = "correct value";
class B extends A {
foo() { return super.bar; }
}
return B;
}
const b1 = createClasses();
%PrepareFunctionForOptimization(b1.prototype.foo);
const b2 = createClasses();
%PrepareFunctionForOptimization(b2.prototype.foo);
class c1 extends b1 {};
class c2 extends b2 {};
const objects = [new c1(), new c2()];
// Fill in the feedback.
for (o of objects) {
const r = o.foo();
assertEquals("correct value", r);
}
%OptimizeFunctionOnNextCall(b1.prototype.foo);
%OptimizeFunctionOnNextCall(b2.prototype.foo);
// Test the optimized function.
for (o of objects) {
const r = o.foo();
assertEquals("correct value", r);
}
assertOptimized(b1.prototype.foo);
assertOptimized(b2.prototype.foo);
})();
(function TestHomeObjectProtoIsGlobalThis() {
class A {}
class B extends A {
foo() { return super.bar; }
}
B.prototype.__proto__ = globalThis;
globalThis.bar = "correct value";
%PrepareFunctionForOptimization(B.prototype.foo);
let o = new B();
// Fill in the feedback.
let r = o.foo();
assertEquals("correct value", r);
%OptimizeFunctionOnNextCall(B.prototype.foo);
// Test the optimized function.
r = o.foo();
assertEquals("correct value", r);
// Assert that the function was not deoptimized.
assertOptimized(B.prototype.foo);
globalThis.bar = "new value";
r = o.foo();
assertEquals("new value", r);
})();
(function TestMegamorphic() {
class A {}
A.prototype.bar = "wrong value: A.prototype.bar";
class B extends A {}
B.prototype.bar = "correct value";
class C extends B {}
class D extends C {
foo() { return super.bar; }
}
%PrepareFunctionForOptimization(D.prototype.foo);
D.prototype.bar = "wrong value: D.prototype.bar";
const o = new D();
// Create objects which will act as the "home object's prototype" later.
const prototypes = [{"a": 0}, {"b": 0}, {"c": 0}, {"d": 0}, {"e": 0},
{"f": 0}, {"g": 0}, {"e": 0}];
for (p of prototypes) {
p.__proto__ = B.prototype;
}
// Fill in the feedback (megamorphic).
for (p of prototypes) {
D.prototype.__proto__ = p;
const r = o.foo();
assertEquals("correct value", r);
}
%OptimizeFunctionOnNextCall(D.prototype.foo);
// Test the optimized function - don't change the home object's proto any
// more.
let r = o.foo();
assertEquals("correct value", r);
// Assert that the function was not deoptimized.
assertOptimized(D.prototype.foo);
})();
(function TestMegamorphicWithGetter() {
class A {}
A.prototype.bar = "wrong value: A.prototype.bar";
class B extends A {
get bar() {
return this.test_value;
}
};
class C extends B {}
class D extends C {
foo() { return super.bar;}
}
%PrepareFunctionForOptimization(D.prototype.foo);
D.prototype.bar = "wrong value: D.prototype.bar";
const o = new D();
o.test_value = "correct value";
// Create objects which will act as the "home object's prototype" later.
const prototypes = [{"a": 0}, {"b": 0}, {"c": 0}, {"d": 0}, {"e": 0},
{"f": 0}, {"g": 0}, {"e": 0}];
for (p of prototypes) {
p.__proto__ = B.prototype;
}
// Fill in the feedback (megamorphic).
for (p of prototypes) {
D.prototype.__proto__ = p;
const r = o.foo();
assertEquals("correct value", r);
}
%OptimizeFunctionOnNextCall(D.prototype.foo);
// Test the optimized function - don't change the home object's proto any
// more.
const r = o.foo();
assertEquals("correct value", r);
})();
(function TestHomeObjectProtoIsGlobalThisGetterProperty() {
class A {}
class B extends A {
foo() { return super.bar; }
}
B.prototype.__proto__ = globalThis;
Object.defineProperty(globalThis, "bar", {get: function() { return this.this_value; }});
%PrepareFunctionForOptimization(B.prototype.foo);
let o = new B();
o.this_value = "correct value";
// Fill in the feedback.
let r = o.foo();
assertEquals("correct value", r);
%OptimizeFunctionOnNextCall(B.prototype.foo);
// Test the optimized function.
r = o.foo();
assertEquals("correct value", r);
// Assert that the function was not deoptimized.
assertOptimized(B.prototype.foo);
})();
(function TestHomeObjectProtoIsFunctionAndPropertyIsPrototype() {
// There are special optimizations for accessing Function.prototype. Test
// that super property access which ends up accessing it works.
class A {}
class B extends A {
foo() { return super.prototype; }
}
function f() {}
B.prototype.__proto__ = f;
%PrepareFunctionForOptimization(B.prototype.foo);
let o = new B();
// Fill in the feedback.
let r = o.foo();
assertEquals(f.prototype, r);
%OptimizeFunctionOnNextCall(B.prototype.foo);
// Test the optimized function.
r = o.foo();
assertEquals(f.prototype, r);
// Assert that the function was not deoptimized.
assertOptimized(B.prototype.foo);
})();