diff --git a/src/codegen/code-stub-assembler.cc b/src/codegen/code-stub-assembler.cc index bab7b2c375..15b109393f 100644 --- a/src/codegen/code-stub-assembler.cc +++ b/src/codegen/code-stub-assembler.cc @@ -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 has_class_fields = - HasProperty(context, constructor.value(), ClassFieldsSymbolConstant(), - kHasProperty); - GotoIf(IsTrue(has_class_fields), found_something_else); + const TNode shared_function_info = + LoadObjectField( + CAST(constructor.value()), JSFunction::kSharedFunctionInfoOffset); + const TNode has_class_fields = + DecodeWord32( + LoadObjectField(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 function_context = diff --git a/src/compiler/bytecode-graph-builder.cc b/src/compiler/bytecode-graph-builder.cc index be5a9bca7d..219c915a6f 100644 --- a/src/compiler/bytecode-graph-builder.cc +++ b/src/compiler/bytecode-graph-builder.cc @@ -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) { diff --git a/src/compiler/heap-refs.cc b/src/compiler/heap-refs.cc index c428f056ea..f75dd3743c 100644 --- a/src/compiler/heap-refs.cc +++ b/src/compiler/heap-refs.cc @@ -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 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()); } diff --git a/src/compiler/heap-refs.h b/src/compiler/heap-refs.h index 1076a1bafd..a9b74089ad 100644 --- a/src/compiler/heap-refs.h +++ b/src/compiler/heap-refs.h @@ -527,6 +527,8 @@ class ContextRef : public HeapObjectRef { // Only returns a value if the index is valid for this ContextRef. base::Optional 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 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) diff --git a/src/compiler/js-generic-lowering.cc b/src/compiler/js-generic-lowering.cc index 4d20ea9294..c79d095690 100644 --- a/src/compiler/js-generic-lowering.cc +++ b/src/compiler/js-generic-lowering.cc @@ -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); } diff --git a/src/compiler/js-native-context-specialization.cc b/src/compiler/js-native-context-specialization.cc index 4012cffe02..54a9cb8de7 100644 --- a/src/compiler/js-native-context-specialization.cc +++ b/src/compiler/js-native-context-specialization.cc @@ -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(); diff --git a/src/compiler/js-native-context-specialization.h b/src/compiler/js-native-context-specialization.h index dc31266434..63c8297f3a 100644 --- a/src/compiler/js-native-context-specialization.h +++ b/src/compiler/js-native-context-specialization.h @@ -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); diff --git a/src/compiler/js-operator.cc b/src/compiler/js-operator.cc index 3945a8730f..cca4d3fe15 100644 --- a/src/compiler/js-operator.cc +++ b/src/compiler/js-operator.cc @@ -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) diff --git a/src/compiler/js-operator.h b/src/compiler/js-operator.h index 12408aea6e..939e86917b 100644 --- a/src/compiler/js-operator.h +++ b/src/compiler/js-operator.h @@ -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 diff --git a/src/compiler/opcodes.h b/src/compiler/opcodes.h index 9853c06f55..9903d067b8 100644 --- a/src/compiler/opcodes.h +++ b/src/compiler/opcodes.h @@ -183,7 +183,8 @@ V(JSStoreInArrayLiteral) \ V(JSDeleteProperty) \ V(JSHasProperty) \ - V(JSGetSuperConstructor) + V(JSGetSuperConstructor) \ + V(JSFindNonDefaultConstructor) #define JS_CONTEXT_OP_LIST(V) \ V(JSHasContextExtension) \ diff --git a/src/compiler/operator-properties.cc b/src/compiler/operator-properties.cc index 0389822629..4a9a166d00 100644 --- a/src/compiler/operator-properties.cc +++ b/src/compiler/operator-properties.cc @@ -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: diff --git a/src/compiler/typer.cc b/src/compiler/typer.cc index 228e315be4..b3df93abca 100644 --- a/src/compiler/typer.cc +++ b/src/compiler/typer.cc @@ -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(); diff --git a/src/compiler/types.cc b/src/compiler/types.cc index a59a2f2e0a..004495a286 100644 --- a/src/compiler/types.cc +++ b/src/compiler/types.cc @@ -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)); diff --git a/src/compiler/types.h b/src/compiler/types.h index dd7203046c..e9353682eb 100644 --- a/src/compiler/types.h +++ b/src/compiler/types.h @@ -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); diff --git a/src/compiler/verifier.cc b/src/compiler/verifier.cc index 1404b8e6ae..4f0978886a 100644 --- a/src/compiler/verifier.cc +++ b/src/compiler/verifier.cc @@ -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; diff --git a/test/mjsunit/compiler/omit-default-ctors-array-iterator.js b/test/mjsunit/compiler/omit-default-ctors-array-iterator.js new file mode 100644 index 0000000000..517ed9b611 --- /dev/null +++ b/test/mjsunit/compiler/omit-default-ctors-array-iterator.js @@ -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; +})(); diff --git a/test/mjsunit/compiler/omit-default-ctors.js b/test/mjsunit/compiler/omit-default-ctors.js new file mode 100644 index 0000000000..c4a075dab1 --- /dev/null +++ b/test/mjsunit/compiler/omit-default-ctors.js @@ -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. +})(); diff --git a/test/mjsunit/omit-default-ctors-array-iterator.js b/test/mjsunit/omit-default-ctors-array-iterator.js index cc9fe54eb9..3b26ff774a 100644 --- a/test/mjsunit/omit-default-ctors-array-iterator.js +++ b/test/mjsunit/omit-default-ctors-array-iterator.js @@ -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. diff --git a/test/mjsunit/omit-default-ctors.js b/test/mjsunit/omit-default-ctors.js index 02b17da343..60c35839d0 100644 --- a/test/mjsunit/omit-default-ctors.js +++ b/test/mjsunit/omit-default-ctors.js @@ -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() {