[compiler] Omit calling default ctors

I.e., implement the TurboFan handler for the FindNonDefaultConstructor
bytecode.

Bug: v8:13091
Change-Id: I021b5d24817b47e3ce86cc1ac1377056cfd5e2a5
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/3885892
Commit-Queue: Marja Hölttä <marja@chromium.org>
Reviewed-by: Jakob Linke <jgruber@chromium.org>
Cr-Commit-Position: refs/heads/main@{#83426}
This commit is contained in:
Marja Hölttä 2022-09-26 14:42:35 +02:00 committed by V8 LUCI CQ
parent 5a7977a3cf
commit 8e72e03d1e
19 changed files with 951 additions and 14 deletions

View File

@ -13848,10 +13848,16 @@ void CodeStubAssembler::FindNonDefaultConstructor(
GotoIfNot(IsJSFunction(CAST(constructor.value())), found_something_else);
// If there are class fields, bail out. TODO(v8:13091): Handle them here.
TNode<Oddball> has_class_fields =
HasProperty(context, constructor.value(), ClassFieldsSymbolConstant(),
kHasProperty);
GotoIf(IsTrue(has_class_fields), found_something_else);
const TNode<SharedFunctionInfo> shared_function_info =
LoadObjectField<SharedFunctionInfo>(
CAST(constructor.value()), JSFunction::kSharedFunctionInfoOffset);
const TNode<Uint32T> has_class_fields =
DecodeWord32<SharedFunctionInfo::RequiresInstanceMembersInitializerBit>(
LoadObjectField<Uint32T>(shared_function_info,
SharedFunctionInfo::kFlagsOffset));
GotoIf(Word32NotEqual(has_class_fields, Int32Constant(0)),
found_something_else);
// If there are private methods, bail out. TODO(v8:13091): Handle them here.
TNode<Context> function_context =

View File

@ -3230,8 +3230,17 @@ void BytecodeGraphBuilder::VisitGetSuperConstructor() {
}
void BytecodeGraphBuilder::VisitFindNonDefaultConstructor() {
// TODO(v8:13091): Implement.
CHECK(false);
Node* this_function =
environment()->LookupRegister(bytecode_iterator().GetRegisterOperand(0));
Node* new_target =
environment()->LookupRegister(bytecode_iterator().GetRegisterOperand(1));
Node* node = NewNode(javascript()->FindNonDefaultConstructor(), this_function,
new_target);
environment()->BindRegistersToProjections(
bytecode_iterator().GetRegisterOperand(2), node,
Environment::kAttachFrameState);
}
void BytecodeGraphBuilder::BuildCompareOp(const Operator* op) {

View File

@ -1561,6 +1561,7 @@ ObjectRef CallHandlerInfoRef::data() const {
HEAP_ACCESSOR_C(ScopeInfo, int, ContextLength)
HEAP_ACCESSOR_C(ScopeInfo, bool, HasContextExtensionSlot)
HEAP_ACCESSOR_C(ScopeInfo, bool, HasOuterScopeInfo)
HEAP_ACCESSOR_C(ScopeInfo, bool, ClassScopeHasPrivateBrand)
ScopeInfoRef ScopeInfoRef::OuterScopeInfo() const {
return MakeRefAssumeMemoryFence(broker(), object()->OuterScopeInfo());
@ -1701,7 +1702,7 @@ ZoneVector<const CFunctionInfo*> FunctionTemplateInfoRef::c_signatures() const {
bool StringRef::IsSeqString() const { return object()->IsSeqString(); }
ScopeInfoRef NativeContextRef::scope_info() const {
ScopeInfoRef ContextRef::scope_info() const {
// The scope_info is immutable after initialization.
return MakeRefAssumeMemoryFence(broker(), object()->scope_info());
}

View File

@ -527,6 +527,8 @@ class ContextRef : public HeapObjectRef {
// Only returns a value if the index is valid for this ContextRef.
base::Optional<ObjectRef> get(int index) const;
ScopeInfoRef scope_info() const;
};
#define BROKER_NATIVE_CONTEXT_FIELDS(V) \
@ -584,7 +586,6 @@ class NativeContextRef : public ContextRef {
BROKER_NATIVE_CONTEXT_FIELDS(DECL_ACCESSOR)
#undef DECL_ACCESSOR
ScopeInfoRef scope_info() const;
MapRef GetFunctionMapFromIndex(int index) const;
MapRef GetInitialJSArrayMap(ElementsKind kind) const;
base::Optional<JSFunctionRef> GetConstructorFunction(const MapRef& map) const;
@ -879,6 +880,7 @@ class ScopeInfoRef : public HeapObjectRef {
int ContextLength() const;
bool HasOuterScopeInfo() const;
bool HasContextExtensionSlot() const;
bool ClassScopeHasPrivateBrand() const;
ScopeInfoRef OuterScopeInfo() const;
};
@ -899,6 +901,7 @@ class ScopeInfoRef : public HeapObjectRef {
V(int, StartPosition) \
V(bool, is_compiled) \
V(bool, IsUserJavaScript) \
V(bool, requires_instance_members_initializer) \
IF_WASM(V, const wasm::WasmModule*, wasm_module) \
IF_WASM(V, const wasm::FunctionSig*, wasm_function_signature)

View File

@ -552,6 +552,10 @@ void JSGenericLowering::LowerJSGetSuperConstructor(Node* node) {
AccessBuilder::ForMapPrototype()));
}
void JSGenericLowering::LowerJSFindNonDefaultConstructor(Node* node) {
ReplaceWithBuiltinCall(node, Builtin::kFindNonDefaultConstructor);
}
void JSGenericLowering::LowerJSHasInPrototypeChain(Node* node) {
ReplaceWithRuntimeCall(node, Runtime::kHasInPrototypeChain);
}

View File

@ -80,6 +80,8 @@ Reduction JSNativeContextSpecialization::Reduce(Node* node) {
return ReduceJSAsyncFunctionResolve(node);
case IrOpcode::kJSGetSuperConstructor:
return ReduceJSGetSuperConstructor(node);
case IrOpcode::kJSFindNonDefaultConstructor:
return ReduceJSFindNonDefaultConstructor(node);
case IrOpcode::kJSInstanceOf:
return ReduceJSInstanceOf(node);
case IrOpcode::kJSHasInPrototypeChain:
@ -540,6 +542,108 @@ Reduction JSNativeContextSpecialization::ReduceJSGetSuperConstructor(
return NoChange();
}
Reduction JSNativeContextSpecialization::ReduceJSFindNonDefaultConstructor(
Node* node) {
JSFindNonDefaultConstructorNode n(node);
Node* this_function = n.this_function();
Node* new_target = n.new_target();
Node* effect = n.effect();
Control control = n.control();
// TODO(v8:13091): Don't produce incomplete stack traces when debug is active.
// We already deopt when a breakpoint is set. But it would be even nicer to
// avoid producting incomplete stack traces when when debug is active, even if
// there are no breakpoints - then a user inspecting stack traces via Dev
// Tools would always see the full stack trace.
// Check if the input is a known JSFunction.
HeapObjectMatcher m(this_function);
if (!m.HasResolvedValue() || !m.Ref(broker()).IsJSFunction()) {
return NoChange();
}
JSFunctionRef this_function_ref = m.Ref(broker()).AsJSFunction();
MapRef function_map = this_function_ref.map();
HeapObjectRef current = function_map.prototype();
Node* return_value;
Node* ctor_or_instance;
// Walk the class inheritance tree until we find a ctor which is not a default
// derived ctor.
while (true) {
if (!current.IsJSFunction()) {
return NoChange();
}
JSFunctionRef current_function = current.AsJSFunction();
// If there are class fields, bail out. TODO(v8:13091): Handle them here.
if (current_function.shared().requires_instance_members_initializer()) {
return NoChange();
}
// If there are private methods, bail out. TODO(v8:13091): Handle them here.
if (current_function.context().scope_info().ClassScopeHasPrivateBrand()) {
return NoChange();
}
FunctionKind kind = current_function.shared().kind();
if (kind != FunctionKind::kDefaultDerivedConstructor) {
// The hierarchy walk will end here; this is the last change to bail out
// before creating new nodes.
if (!dependencies()->DependOnArrayIteratorProtector()) {
return NoChange();
}
if (kind == FunctionKind::kDefaultBaseConstructor) {
return_value = jsgraph()->BooleanConstant(true);
// Generate a builtin call for creating the instance.
Node* constructor = jsgraph()->Constant(current_function);
effect = ctor_or_instance = graph()->NewNode(
jsgraph()->javascript()->Create(), constructor, new_target,
n.context(), n.frame_state(), effect, control);
} else {
return_value = jsgraph()->BooleanConstant(false);
ctor_or_instance = jsgraph()->Constant(current_function);
}
break;
}
// Keep walking up the class tree.
current = current_function.map().prototype();
}
dependencies()->DependOnStablePrototypeChain(function_map,
WhereToStart::kStartAtReceiver);
// Update the uses of {node}.
for (Edge edge : node->use_edges()) {
Node* const user = edge.from();
if (NodeProperties::IsEffectEdge(edge)) {
edge.UpdateTo(effect);
} else if (NodeProperties::IsControlEdge(edge)) {
edge.UpdateTo(control);
} else {
DCHECK(NodeProperties::IsValueEdge(edge));
switch (ProjectionIndexOf(user->op())) {
case 0:
Replace(user, return_value);
break;
case 1:
Replace(user, ctor_or_instance);
break;
default:
UNREACHABLE();
}
}
}
node->Kill();
return Replace(return_value);
}
Reduction JSNativeContextSpecialization::ReduceJSInstanceOf(Node* node) {
JSInstanceOfNode n(node);
FeedbackParameter const& p = n.Parameters();

View File

@ -75,6 +75,7 @@ class V8_EXPORT_PRIVATE JSNativeContextSpecialization final
Reduction ReduceJSAsyncFunctionReject(Node* node);
Reduction ReduceJSAsyncFunctionResolve(Node* node);
Reduction ReduceJSGetSuperConstructor(Node* node);
Reduction ReduceJSFindNonDefaultConstructor(Node* node);
Reduction ReduceJSInstanceOf(Node* node);
Reduction ReduceJSHasInPrototypeChain(Node* node);
Reduction ReduceJSOrdinaryHasInstance(Node* node);

View File

@ -770,6 +770,7 @@ Type JSWasmCallNode::TypeForWasmReturnType(const wasm::ValueType& type) {
V(RejectPromise, Operator::kNoDeopt | Operator::kNoThrow, 3, 1) \
V(ResolvePromise, Operator::kNoDeopt | Operator::kNoThrow, 2, 1) \
V(GetSuperConstructor, Operator::kNoWrite | Operator::kNoThrow, 1, 1) \
V(FindNonDefaultConstructor, Operator::kNoProperties, 2, 2) \
V(ParseInt, Operator::kNoProperties, 2, 1) \
V(RegExpTest, Operator::kNoProperties, 2, 1)

View File

@ -1040,6 +1040,8 @@ class V8_EXPORT_PRIVATE JSOperatorBuilder final
const Operator* GetSuperConstructor();
const Operator* FindNonDefaultConstructor();
const Operator* CreateGeneratorObject();
const Operator* LoadGlobal(const NameRef& name,
@ -1757,6 +1759,20 @@ class JSForInNextNode final : public JSNodeWrapperBase {
#undef INPUTS
};
class JSFindNonDefaultConstructorNode final : public JSNodeWrapperBase {
public:
explicit constexpr JSFindNonDefaultConstructorNode(Node* node)
: JSNodeWrapperBase(node) {
DCHECK_EQ(IrOpcode::kJSFindNonDefaultConstructor, node->opcode());
}
#define INPUTS(V) \
V(ThisFunction, this_function, 0, Object) \
V(NewTarget, new_target, 1, Object)
INPUTS(DEFINE_INPUT_ACCESSORS)
#undef INPUTS
};
#undef DEFINE_INPUT_ACCESSORS
} // namespace compiler

View File

@ -183,7 +183,8 @@
V(JSStoreInArrayLiteral) \
V(JSDeleteProperty) \
V(JSHasProperty) \
V(JSGetSuperConstructor)
V(JSGetSuperConstructor) \
V(JSFindNonDefaultConstructor)
#define JS_CONTEXT_OP_LIST(V) \
V(JSHasContextExtension) \

View File

@ -91,6 +91,7 @@ bool OperatorProperties::NeedsExactContext(const Operator* op) {
case IrOpcode::kJSSetNamedProperty:
case IrOpcode::kJSDefineNamedOwnProperty:
case IrOpcode::kJSSetKeyedProperty:
case IrOpcode::kJSFindNonDefaultConstructor:
return true;
case IrOpcode::kJSAsyncFunctionEnter:
@ -239,6 +240,7 @@ bool OperatorProperties::HasFrameStateInput(const Operator* op) {
case IrOpcode::kJSStackCheck:
case IrOpcode::kJSDebugger:
case IrOpcode::kJSGetSuperConstructor:
case IrOpcode::kJSFindNonDefaultConstructor:
case IrOpcode::kJSBitwiseNot:
case IrOpcode::kJSDecrement:
case IrOpcode::kJSIncrement:

View File

@ -1470,6 +1470,10 @@ Type Typer::Visitor::TypeJSGetSuperConstructor(Node* node) {
return Type::NonInternal();
}
Type Typer::Visitor::TypeJSFindNonDefaultConstructor(Node* node) {
return Type::Tuple(Type::Boolean(), Type::Object(), zone());
}
// JS context operators.
Type Typer::Visitor::TypeJSHasContextExtension(Node* node) {
return Type::Boolean();

View File

@ -1121,6 +1121,13 @@ Type Type::Tuple(Type first, Type second, Type third, Zone* zone) {
return FromTypeBase(tuple);
}
Type Type::Tuple(Type first, Type second, Zone* zone) {
TupleType* tuple = TupleType::New(2, zone);
tuple->InitElement(0, first);
tuple->InitElement(1, second);
return FromTypeBase(tuple);
}
// static
Type Type::OtherNumberConstant(double value, Zone* zone) {
return FromTypeBase(OtherNumberConstantType::New(value, zone));

View File

@ -423,6 +423,7 @@ class V8_EXPORT_PRIVATE Type {
static Type Constant(double value, Zone* zone);
static Type Range(double min, double max, Zone* zone);
static Type Tuple(Type first, Type second, Type third, Zone* zone);
static Type Tuple(Type first, Type second, Zone* zone);
static Type Union(Type type1, Type type2, Zone* zone);
static Type Intersect(Type type1, Type type2, Zone* zone);

View File

@ -790,7 +790,10 @@ void Verifier::Visitor::Check(Node* node, const AllNodes& all) {
CheckValueInputIs(node, 0, Type::Any());
CheckTypeIs(node, Type::NonInternal());
break;
case IrOpcode::kJSFindNonDefaultConstructor:
CheckValueInputIs(node, 0, Type::Any());
CheckValueInputIs(node, 1, Type::Any());
break;
case IrOpcode::kJSHasContextExtension:
CheckTypeIs(node, Type::Boolean());
break;

View File

@ -0,0 +1,35 @@
// Copyright 2022 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: --omit-default-ctors --allow-natives-syntax --no-maglev --turbofan
// Flags: --no-always-turbofan
// TODO(v8:13091): Enable Maglev.
// This behavior is not spec compliant, see crbug.com/v8/13249.
(function ArrayIteratorMonkeyPatched() {
let iterationCount = 0;
const oldIterator = Array.prototype[Symbol.iterator];
Array.prototype[Symbol.iterator] =
function () { ++iterationCount; return oldIterator.call(this); };
class A {}
class B extends A {}
class C extends B {}
%PrepareFunctionForOptimization(C);
new C();
%OptimizeFunctionOnNextCall(C);
// C default ctor doing "...args" and B default ctor doing "...args".
assertEquals(2, iterationCount);
new C();
// C default ctor doing "...args" and B default ctor doing "...args".
assertEquals(4, iterationCount);
assertTrue(isTurboFanned(C)); // No deopt.
Array.prototype[Symbol.iterator] = oldIterator;
})();

View File

@ -0,0 +1,741 @@
// Copyright 2022 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: --omit-default-ctors --allow-natives-syntax --no-maglev --turbofan
// Flags: --no-always-turbofan
// TODO(v8:13091): Enable Maglev.
(function OmitDefaultBaseCtor() {
class A {}; // default base ctor -> will be omitted
class B extends A {};
%PrepareFunctionForOptimization(B);
new B();
%OptimizeFunctionOnNextCall(B);
const o = new B();
assertSame(B.prototype, o.__proto__);
assertTrue(isTurboFanned(B)); // No deopt.
})();
(function OmitDefaultDerivedCtor() {
class A { constructor() {} };
class B extends A {}; // default derived ctor -> will be omitted
class C extends B {};
%PrepareFunctionForOptimization(C);
new C();
%OptimizeFunctionOnNextCall(C);
const o = new C();
assertSame(C.prototype, o.__proto__);
assertTrue(isTurboFanned(C)); // No deopt.
})();
(function OmitDefaultBaseAndDerivedCtor() {
class A {}; // default base ctor -> will be omitted
class B extends A {}; // default derived ctor -> will be omitted
class C extends B {};
%PrepareFunctionForOptimization(C);
new C();
%OptimizeFunctionOnNextCall(C);
const o = new C();
assertSame(C.prototype, o.__proto__);
assertTrue(isTurboFanned(C)); // No deopt.
})();
(function OmitDefaultBaseCtorWithExplicitSuper() {
class A {}; // default base ctor -> will be omitted
class B extends A { constructor() { super(); } };
%PrepareFunctionForOptimization(B);
new B();
%OptimizeFunctionOnNextCall(B);
const o = new B();
assertSame(B.prototype, o.__proto__);
assertTrue(isTurboFanned(B)); // No deopt.
})();
(function OmitDefaultDerivedCtorWithExplicitSuper() {
class A { constructor() {} };
class B extends A {}; // default derived ctor -> will be omitted
class C extends B { constructor() { super(); } };
%PrepareFunctionForOptimization(C);
new C();
%OptimizeFunctionOnNextCall(C);
const o = new C();
assertSame(C.prototype, o.__proto__);
assertTrue(isTurboFanned(C)); // No deopt.
})();
(function OmitDefaultBaseAndDerivedCtorWithExplicitSuper() {
class A {}; // default base ctor -> will be omitted
class B extends A {}; // default derived ctor -> will be omitted
class C extends B { constructor() { super(); } };
%PrepareFunctionForOptimization(C);
new C();
%OptimizeFunctionOnNextCall(C);
const o = new C();
assertSame(C.prototype, o.__proto__);
assertTrue(isTurboFanned(C)); // No deopt.
})();
(function OmitDefaultBaseCtorWithExplicitSuperAndNonFinalSpread() {
class A {}; // default base ctor -> will be omitted
class B extends A { constructor(...args) { super(1, ...args, 2); } };
%PrepareFunctionForOptimization(B);
new B();
%OptimizeFunctionOnNextCall(B);
const o = new B(3, 4);
assertSame(B.prototype, o.__proto__);
// See https://bugs.chromium.org/p/v8/issues/detail?id=13310
// assertTrue(isTurboFanned(B)); // No deopt.
// This assert will fail when the above bug is fixed:
assertFalse(isTurboFanned(B));
})();
(function OmitDefaultDerivedCtorWithExplicitSuperAndNonFinalSpread() {
class A { constructor() {} };
class B extends A {}; // default derived ctor -> will be omitted
class C extends B { constructor(...args) { super(1, ...args, 2); } };
%PrepareFunctionForOptimization(C);
new C();
%OptimizeFunctionOnNextCall(C);
const o = new C(3, 4);
assertSame(C.prototype, o.__proto__);
// See https://bugs.chromium.org/p/v8/issues/detail?id=13310
// assertTrue(isTurboFanned(C)); // No deopt.
// This assert will fail when the above bug is fixed:
assertFalse(isTurboFanned(C));
})();
(function OmitDefaultBaseAndDerivedCtorWithExplicitSuperAndNonFinalSpread() {
class A {}; // default base ctor -> will be omitted
class B extends A {}; // default derived ctor -> will be omitted
class C extends B { constructor(...args) { super(1, ...args, 2); } };
%PrepareFunctionForOptimization(C);
new C();
%OptimizeFunctionOnNextCall(C);
const o = new C(3, 4);
assertSame(C.prototype, o.__proto__);
// See https://bugs.chromium.org/p/v8/issues/detail?id=13310
// assertTrue(isTurboFanned(C)); // No deopt.
// This assert will fail when the above bug is fixed:
assertFalse(isTurboFanned(C));
})();
(function NonDefaultBaseConstructorCalled() {
let ctorCallCount = 0;
let lastArgs;
class Base {
constructor(...args) {
++ctorCallCount;
this.baseTagged = true;
lastArgs = args;
}
};
// Nothing will be omitted.
class A extends Base {};
%PrepareFunctionForOptimization(A);
new A();
%OptimizeFunctionOnNextCall(A);
const a = new A(1, 2, 3);
assertEquals(2, ctorCallCount);
assertEquals([1, 2, 3], lastArgs);
assertTrue(a.baseTagged);
assertTrue(isTurboFanned(A)); // No deopt.
// 'A' default ctor will be omitted.
class B1 extends A {};
%PrepareFunctionForOptimization(B1);
new B1();
%OptimizeFunctionOnNextCall(B1);
const b1 = new B1(4, 5, 6);
assertEquals(4, ctorCallCount);
assertEquals([4, 5, 6], lastArgs);
assertTrue(b1.baseTagged);
assertTrue(isTurboFanned(B1)); // No deopt.
// The same test with non-final spread; 'A' default ctor will be omitted.
class B2 extends A {
constructor(...args) { super(1, ...args, 2); }
};
%PrepareFunctionForOptimization(B2);
new B2();
%OptimizeFunctionOnNextCall(B2);
const b2 = new B2(4, 5, 6);
assertEquals(6, ctorCallCount);
assertEquals([1, 4, 5, 6, 2], lastArgs);
assertTrue(b2.baseTagged);
// See https://bugs.chromium.org/p/v8/issues/detail?id=13310
// assertTrue(isTurboFanned(B2)); // No deopt.
// This assert will fail when the above bug is fixed:
assertFalse(isTurboFanned(B2)); // No deopt.
})();
(function NonDefaultDerivedConstructorCalled() {
let ctorCallCount = 0;
let lastArgs;
class Base {};
class Derived extends Base {
constructor(...args) {
super();
++ctorCallCount;
this.derivedTagged = true;
lastArgs = args;
}
};
// Nothing will be omitted.
class A extends Derived {};
%PrepareFunctionForOptimization(A);
new A();
%OptimizeFunctionOnNextCall(A);
const a = new A(1, 2, 3);
assertEquals(2, ctorCallCount);
assertEquals([1, 2, 3], lastArgs);
assertTrue(a.derivedTagged);
assertTrue(isTurboFanned(A)); // No deopt.
// 'A' default ctor will be omitted.
class B1 extends A {};
%PrepareFunctionForOptimization(B1);
new B1();
%OptimizeFunctionOnNextCall(B1);
const b1 = new B1(4, 5, 6);
assertEquals(4, ctorCallCount);
assertEquals([4, 5, 6], lastArgs);
assertTrue(b1.derivedTagged);
assertTrue(isTurboFanned(B1)); // No deopt.
// The same test with non-final spread. 'A' default ctor will be omitted.
class B2 extends A {
constructor(...args) { super(1, ...args, 2); }
};
%PrepareFunctionForOptimization(B2);
new B2();
%OptimizeFunctionOnNextCall(B2);
const b2 = new B2(4, 5, 6);
assertEquals(6, ctorCallCount);
assertEquals([1, 4, 5, 6, 2], lastArgs);
assertTrue(b2.derivedTagged);
// See https://bugs.chromium.org/p/v8/issues/detail?id=13310
// assertTrue(isTurboFanned(B2)); // No deopt.
// This assert will fail when the above bug is fixed:
assertFalse(isTurboFanned(B2)); // No deopt.
})();
(function BaseFunctionCalled() {
let baseFunctionCallCount = 0;
function BaseFunction() {
++baseFunctionCallCount;
this.baseTagged = true;
}
class A1 extends BaseFunction {};
%PrepareFunctionForOptimization(A1);
new A1();
%OptimizeFunctionOnNextCall(A1);
const a1 = new A1();
assertEquals(2, baseFunctionCallCount);
assertTrue(a1.baseTagged);
assertTrue(isTurboFanned(A1)); // No deopt.
class A2 extends BaseFunction {
constructor(...args) { super(1, ...args, 2); }
};
%PrepareFunctionForOptimization(A2);
new A2();
%OptimizeFunctionOnNextCall(A2);
const a2 = new A2();
assertEquals(4, baseFunctionCallCount);
assertTrue(a2.baseTagged);
assertTrue(isTurboFanned(A2)); // No deopt.
})();
(function NonSuperclassCtor() {
class A {};
class B extends A {};
class C extends B {};
class D1 extends C {};
class D2 extends C { constructor(...args) { super(1, ...args, 2); }};
%PrepareFunctionForOptimization(C);
%PrepareFunctionForOptimization(D1);
%PrepareFunctionForOptimization(D2);
new C();
new D1();
new D2();
%OptimizeFunctionOnNextCall(C);
%OptimizeFunctionOnNextCall(D1);
%OptimizeFunctionOnNextCall(D2);
// Install an object which is not a constructor into the class hierarchy.
C.__proto__ = {};
assertThrows(() => { new C(); }, TypeError);
assertThrows(() => { new D1(); }, TypeError);
assertThrows(() => { new D2(); }, TypeError);
})();
(function ArgumentsEvaluatedBeforeNonSuperclassCtorDetected() {
class A {};
class B extends A {};
class C extends B {};
class D1 extends C {};
class D2 extends C { constructor(...args) { super(1, ...args, 2); }};
%PrepareFunctionForOptimization(C);
%PrepareFunctionForOptimization(D1);
%PrepareFunctionForOptimization(D2);
new C();
new D1();
new D2();
%OptimizeFunctionOnNextCall(C);
%OptimizeFunctionOnNextCall(D1);
%OptimizeFunctionOnNextCall(D2);
// Install an object which is not a constructor into the class hierarchy.
C.__proto__ = {};
let callCount = 0;
function foo() {
++callCount;
}
assertThrows(() => { new C(foo()); }, TypeError);
assertEquals(1, callCount);
assertThrows(() => { new D1(foo()); }, TypeError);
assertEquals(2, callCount);
assertThrows(() => { new D2(foo()); }, TypeError);
assertEquals(3, callCount);
})();
(function ArgumentsEvaluatedBeforeNonSuperclassCtorDetected2() {
class A {};
class B extends A {};
class C extends B {};
class D1 extends C {
constructor() {
super(foo());
}
};
class D2 extends C {
constructor(...args) {
super(...args, foo());
}
};
let callCount = 0;
function foo() {
++callCount;
}
%PrepareFunctionForOptimization(D1);
%PrepareFunctionForOptimization(D2);
new D1();
new D2();
%OptimizeFunctionOnNextCall(D1);
%OptimizeFunctionOnNextCall(D2);
assertEquals(2, callCount);
// Install an object which is not a constructor into the class hierarchy.
C.__proto__ = {};
assertThrows(() => { new D1(); }, TypeError);
assertEquals(3, callCount);
assertThrows(() => { new D2(); }, TypeError);
assertEquals(4, callCount);
})();
(function EvaluatingArgumentsChangesClassHierarchy() {
let ctorCallCount = 0;
class A {};
class B extends A { constructor() {
super();
++ctorCallCount;
}};
class C extends B {};
class D extends C {
constructor() {
super(foo());
}
};
let fooCallCount = 0;
let changeHierarchy = false;
function foo() {
if (changeHierarchy) {
C.__proto__ = A;
C.prototype.__proto__ = A.prototype;
}
++fooCallCount;
}
%PrepareFunctionForOptimization(D);
new D();
assertEquals(1, fooCallCount);
assertEquals(1, ctorCallCount);
%OptimizeFunctionOnNextCall(D);
changeHierarchy = true;
new D();
assertEquals(2, fooCallCount);
assertEquals(1, ctorCallCount);
assertFalse(isTurboFanned(D)); // Deopt.
})();
// The same test as the previous one, but with a ctor with a non-final spread.
(function EvaluatingArgumentsChangesClassHierarchyThisTimeWithNonFinalSpread() {
let ctorCallCount = 0;
class A {};
class B extends A { constructor() {
super();
++ctorCallCount;
}};
class C extends B {};
class D extends C {
constructor(...args) {
super(...args, foo());
}
};
let fooCallCount = 0;
let changeHierarchy = false;
function foo() {
if (changeHierarchy) {
C.__proto__ = A;
C.prototype.__proto__ = A.prototype;
}
++fooCallCount;
}
%PrepareFunctionForOptimization(D);
new D();
assertEquals(1, fooCallCount);
assertEquals(1, ctorCallCount);
%OptimizeFunctionOnNextCall(D);
changeHierarchy = true;
new D();
assertEquals(2, fooCallCount);
assertEquals(1, ctorCallCount);
assertFalse(isTurboFanned(D)); // Deopt.
})();
(function BasePrivateField() {
class A {
#aBrand = true;
isA() {
return #aBrand in this;
}
};
class B extends A {};
class C1 extends B {};
class C2 extends B { constructor(...args) { super(1, ...args, 2); }};
%PrepareFunctionForOptimization(B);
new B();
%OptimizeFunctionOnNextCall(B);
const b = new B();
assertTrue(b.isA());
assertTrue(isTurboFanned(B)); // No deopt.
%PrepareFunctionForOptimization(C1);
new C1();
%OptimizeFunctionOnNextCall(C1);
const c1 = new C1();
assertTrue(c1.isA());
assertTrue(isTurboFanned(C1)); // No deopt.
%PrepareFunctionForOptimization(C2);
new C2();
%OptimizeFunctionOnNextCall(C2);
const c2 = new C2();
assertTrue(c2.isA());
assertTrue(isTurboFanned(C2)); // No deopt.
})();
(function DerivedPrivateField() {
class A {};
class B extends A {
#bBrand = true;
isB() {
return #bBrand in this;
}
};
class C1 extends B {};
class C2 extends B { constructor(...args) { super(1, ...args, 2); }};
%PrepareFunctionForOptimization(C1);
new C1();
%OptimizeFunctionOnNextCall(C1);
const c1 = new C1();
assertTrue(c1.isB());
assertTrue(isTurboFanned(C1)); // No deopt.
%PrepareFunctionForOptimization(C2);
new C2();
%OptimizeFunctionOnNextCall(C2);
const c2 = new C2();
assertTrue(c2.isB());
assertTrue(isTurboFanned(C2)); // No deopt.
})();
(function BasePrivateMethod() {
class A {
#m() { return 'private'; }
callPrivate() {
return this.#m();
}
};
class B extends A {};
class C1 extends B {};
class C2 extends B { constructor(...args) { super(1, ...args, 2); }};
%PrepareFunctionForOptimization(B);
new B();
%OptimizeFunctionOnNextCall(B);
const b = new B();
assertEquals('private', b.callPrivate());
assertTrue(isTurboFanned(B)); // No deopt.
%PrepareFunctionForOptimization(C1);
new C1();
%OptimizeFunctionOnNextCall(C1);
const c1 = new C1();
assertEquals('private', c1.callPrivate());
assertTrue(isTurboFanned(C1)); // No deopt.
%PrepareFunctionForOptimization(C2);
new C2();
%OptimizeFunctionOnNextCall(C2);
const c2 = new C2();
assertEquals('private', c2.callPrivate());
assertTrue(isTurboFanned(C2)); // No deopt.
})();
(function DerivedPrivateMethod() {
class A {};
class B extends A {
#m() { return 'private'; }
callPrivate() {
return this.#m();
}
};
class C1 extends B {};
class C2 extends B { constructor(...args) { super(1, ...args, 2); }};
%PrepareFunctionForOptimization(C1);
new C1();
%OptimizeFunctionOnNextCall(C1);
const c1 = new C1();
assertEquals('private', c1.callPrivate());
assertTrue(isTurboFanned(C1)); // No deopt.
%PrepareFunctionForOptimization(C2);
new C2();
%OptimizeFunctionOnNextCall(C2);
const c2 = new C2();
assertEquals('private', c2.callPrivate());
assertTrue(isTurboFanned(C2)); // No deopt.
})();
(function BasePrivateGetter() {
class A {
get #p() { return 'private'; }
getPrivate() {
return this.#p;
}
};
class B extends A {};
class C1 extends B {};
class C2 extends B { constructor(...args) { super(1, ...args, 2); }};
%PrepareFunctionForOptimization(B);
new B();
%OptimizeFunctionOnNextCall(B);
const b = new B();
assertEquals('private', b.getPrivate());
assertTrue(isTurboFanned(B)); // No deopt.
%PrepareFunctionForOptimization(C1);
new C1();
%OptimizeFunctionOnNextCall(C1);
const c1 = new C1();
assertEquals('private', c1.getPrivate());
assertTrue(isTurboFanned(C1)); // No deopt.
%PrepareFunctionForOptimization(C2);
new C2();
%OptimizeFunctionOnNextCall(C2);
const c2 = new C2();
assertEquals('private', c2.getPrivate());
assertTrue(isTurboFanned(C2)); // No deopt.
})();
(function DerivedPrivateGetter() {
class A {};
class B extends A {
get #p() { return 'private'; }
getPrivate() {
return this.#p;
}
};
class C1 extends B {};
class C2 extends B { constructor(...args) { super(1, ...args, 2); }};
%PrepareFunctionForOptimization(C1);
new C1();
%OptimizeFunctionOnNextCall(C1);
const c1 = new C1();
assertEquals('private', c1.getPrivate());
assertTrue(isTurboFanned(C1)); // No deopt.
%PrepareFunctionForOptimization(C2);
new C2();
%OptimizeFunctionOnNextCall(C2);
const c2 = new C2();
assertEquals('private', c2.getPrivate());
assertTrue(isTurboFanned(C2)); // No deopt.
})();
(function BasePrivateSetter() {
class A {
set #p(value) { this.secret = value; }
setPrivate() {
this.#p = 'private';
}
};
class B extends A {};
class C1 extends B {};
class C2 extends B { constructor(...args) { super(1, ...args, 2); }};
%PrepareFunctionForOptimization(B);
new B();
%OptimizeFunctionOnNextCall(B);
const b = new B();
b.setPrivate();
assertEquals('private', b.secret);
%PrepareFunctionForOptimization(C1);
new C1();
%OptimizeFunctionOnNextCall(C1);
const c1 = new C1();
c1.setPrivate();
assertEquals('private', c1.secret);
assertTrue(isTurboFanned(C1)); // No deopt.
%PrepareFunctionForOptimization(C2);
new C2();
%OptimizeFunctionOnNextCall(C2);
const c2 = new C2();
c2.setPrivate();
assertEquals('private', c2.secret);
assertTrue(isTurboFanned(C2)); // No deopt.
})();
(function DerivedPrivateSetter() {
class A {};
class B extends A {
set #p(value) { this.secret = value; }
setPrivate() {
this.#p = 'private';
}
};
class C1 extends B {};
class C2 extends B { constructor(...args) { super(1, ...args, 2); }};
%PrepareFunctionForOptimization(C1);
new C1();
%OptimizeFunctionOnNextCall(C1);
const c1 = new C1();
c1.setPrivate();
assertEquals('private', c1.secret);
assertTrue(isTurboFanned(C1)); // No deopt.
%PrepareFunctionForOptimization(C2);
new C2();
%OptimizeFunctionOnNextCall(C2);
const c2 = new C2();
c2.setPrivate();
assertEquals('private', c2.secret);
assertTrue(isTurboFanned(C2)); // No deopt.
})();
(function BaseClassFields() {
class A {
aField = true;
};
class B extends A {};
class C1 extends B {};
class C2 extends B { constructor(...args) { super(1, ...args, 2); }};
%PrepareFunctionForOptimization(B);
new B();
%OptimizeFunctionOnNextCall(B);
const b = new B();
assertTrue(b.aField);
%PrepareFunctionForOptimization(C1);
new C1();
%OptimizeFunctionOnNextCall(C1);
const c1 = new C1();
assertTrue(c1.aField);
assertTrue(isTurboFanned(C1)); // No deopt.
%PrepareFunctionForOptimization(C2);
new C2();
%OptimizeFunctionOnNextCall(C2);
const c2 = new C2();
assertTrue(c2.aField);
assertTrue(isTurboFanned(C2)); // No deopt.
})();
(function DerivedClassFields() {
class A {};
class B extends A {
bField = true;
};
class C1 extends B {};
class C2 extends B { constructor(...args) { super(1, ...args, 2); }};
%PrepareFunctionForOptimization(C1);
new C1();
%OptimizeFunctionOnNextCall(C1);
const c1 = new C1();
assertTrue(c1.bField);
assertTrue(isTurboFanned(C1)); // No deopt.
%PrepareFunctionForOptimization(C2);
new C2();
%OptimizeFunctionOnNextCall(C2);
const c2 = new C2();
assertTrue(c2.bField);
assertTrue(isTurboFanned(C2)); // No deopt.
})();

View File

@ -2,9 +2,8 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// Flags: --omit-default-ctors --no-turbofan --no-always-turbofan --no-maglev
// Flags: --omit-default-ctors --no-maglev
// TODO(v8:13091): Enable TurboFan.
// TODO(v8:13091): Enable Maglev.
// This behavior is not spec compliant, see crbug.com/v8/13249.

View File

@ -2,9 +2,8 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// Flags: --omit-default-ctors --no-turbofan --no-always-turbofan --no-maglev
// Flags: --omit-default-ctors --no-maglev
// TODO(v8:13091): Enable TurboFan.
// TODO(v8:13091): Enable Maglev.
(function OmitDefaultBaseCtor() {