Commit b1553b91 authored by Marja Hölttä's avatar Marja Hölttä Committed by V8 LUCI CQ

[interpreter] Omit calling default ctors

If we see a default ctor, walk up the constructors until we find a non-
default one.

Default ctors can only be skipped if there are no class fields / private
brands.

This CL implements the Ignition parts; Sparkplug, Maglev and TF will
be implemented as follow ups. (This is fine, since this feature is
behind a flag.)

Bug: v8:13091
Change-Id: Ie8ca8aedb01bd4b13adf1063332a5cdf41ab358a
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/3804601Reviewed-by: 's avatarLeszek Swirski <leszeks@chromium.org>
Reviewed-by: 's avatarNico Hartmann <nicohartmann@chromium.org>
Commit-Queue: Marja Hölttä <marja@chromium.org>
Cr-Commit-Position: refs/heads/main@{#82872}
parent 168fcef9
......@@ -1160,6 +1160,11 @@ void BaselineCompiler::VisitGetSuperConstructor() {
StoreRegister(0, prototype);
}
void BaselineCompiler::VisitFindNonDefaultConstructor() {
// TODO(v8:13091): Implement.
CHECK(false);
}
namespace {
constexpr Builtin ConvertReceiverModeToCompactBuiltin(
ConvertReceiverMode mode) {
......
......@@ -2747,6 +2747,13 @@ TNode<BoolT> CodeStubAssembler::LoadScopeInfoHasExtensionField(
return IsSetWord<ScopeInfo::HasContextExtensionSlotBit>(value);
}
TNode<BoolT> CodeStubAssembler::LoadScopeInfoClassScopeHasPrivateBrand(
TNode<ScopeInfo> scope_info) {
TNode<IntPtrT> value =
LoadAndUntagObjectField(scope_info, ScopeInfo::kFlagsOffset);
return IsSetWord<ScopeInfo::ClassScopeHasPrivateBrandBit>(value);
}
void CodeStubAssembler::StoreContextElementNoWriteBarrier(
TNode<Context> context, int slot_index, TNode<Object> value) {
int offset = Context::SlotOffset(slot_index);
......@@ -2837,8 +2844,7 @@ TNode<Map> CodeStubAssembler::LoadJSArrayElementsMap(
LoadContextElement(native_context, Context::ArrayMapIndex(kind)));
}
TNode<BoolT> CodeStubAssembler::IsGeneratorFunction(
TNode<JSFunction> function) {
TNode<Uint32T> CodeStubAssembler::LoadFunctionKind(TNode<JSFunction> function) {
const TNode<SharedFunctionInfo> shared_function_info =
LoadObjectField<SharedFunctionInfo>(
function, JSFunction::kSharedFunctionInfoOffset);
......@@ -2847,6 +2853,12 @@ TNode<BoolT> CodeStubAssembler::IsGeneratorFunction(
DecodeWord32<SharedFunctionInfo::FunctionKindBits>(
LoadObjectField<Uint32T>(shared_function_info,
SharedFunctionInfo::kFlagsOffset));
return function_kind;
}
TNode<BoolT> CodeStubAssembler::IsGeneratorFunction(
TNode<JSFunction> function) {
const TNode<Uint32T> function_kind = LoadFunctionKind(function);
// See IsGeneratorFunction(FunctionKind kind).
return IsInRange(
......
......@@ -138,6 +138,7 @@ enum class PrimitiveType { kBoolean, kNumber, kString, kSymbol };
V(array_to_string, array_to_string, ArrayToString) \
V(BooleanMap, boolean_map, BooleanMap) \
V(boolean_to_string, boolean_to_string, BooleanToString) \
V(class_fields_symbol, class_fields_symbol, ClassFieldsSymbol) \
V(ConsOneByteStringMap, cons_one_byte_string_map, ConsOneByteStringMap) \
V(ConsStringMap, cons_string_map, ConsStringMap) \
V(constructor_string, constructor_string, ConstructorString) \
......@@ -1628,6 +1629,8 @@ class V8_EXPORT_PRIVATE CodeStubAssembler
// ScopeInfo:
TNode<ScopeInfo> LoadScopeInfo(TNode<Context> context);
TNode<BoolT> LoadScopeInfoHasExtensionField(TNode<ScopeInfo> scope_info);
TNode<BoolT> LoadScopeInfoClassScopeHasPrivateBrand(
TNode<ScopeInfo> scope_info);
// Context manipulation:
void StoreContextElementNoWriteBarrier(TNode<Context> context, int slot_index,
......@@ -1656,6 +1659,7 @@ class V8_EXPORT_PRIVATE CodeStubAssembler
TNode<NativeContext> native_context);
TNode<BoolT> IsJSFunctionWithPrototypeSlot(TNode<HeapObject> object);
TNode<Uint32T> LoadFunctionKind(TNode<JSFunction> function);
TNode<BoolT> IsGeneratorFunction(TNode<JSFunction> function);
void BranchIfHasPrototypeProperty(TNode<JSFunction> function,
TNode<Int32T> function_map_bit_field,
......
......@@ -3235,6 +3235,11 @@ void BytecodeGraphBuilder::VisitGetSuperConstructor() {
Environment::kAttachFrameState);
}
void BytecodeGraphBuilder::VisitFindNonDefaultConstructor() {
// TODO(v8:13091): Implement.
CHECK(false);
}
void BytecodeGraphBuilder::BuildCompareOp(const Operator* op) {
DCHECK(JSOperator::IsBinaryWithFeedback(op->opcode()));
PrepareEagerCheckpoint();
......
......@@ -628,6 +628,7 @@ DEFINE_BOOL(stress_lazy_source_positions, false,
"collect lazy source positions immediately after lazy compile")
DEFINE_STRING(print_bytecode_filter, "*",
"filter for selecting which functions to print bytecode")
DEFINE_BOOL(omit_default_ctors, false, "omit calling default ctors in bytecode")
#ifdef V8_TRACE_UNOPTIMIZED
DEFINE_BOOL(trace_unoptimized, false,
"trace the bytecodes executed by all unoptimized execution")
......
......@@ -526,6 +526,14 @@ BytecodeArrayBuilder& BytecodeArrayBuilder::GetSuperConstructor(Register out) {
return *this;
}
BytecodeArrayBuilder& BytecodeArrayBuilder::FindNonDefaultConstructor(
Register this_function, Register new_target, Register constructor,
Register instance) {
OutputFindNonDefaultConstructor(this_function, new_target, constructor,
instance);
return *this;
}
BytecodeArrayBuilder& BytecodeArrayBuilder::CompareOperation(
Token::Value op, Register reg, int feedback_slot) {
switch (op) {
......
......@@ -386,6 +386,11 @@ class V8_EXPORT_PRIVATE BytecodeArrayBuilder final {
// throws a TypeError exception.
BytecodeArrayBuilder& GetSuperConstructor(Register out);
BytecodeArrayBuilder& FindNonDefaultConstructor(Register this_function,
Register new_target,
Register constructor,
Register instance);
// Deletes property from an object. This expects that accumulator contains
// the key to be deleted and the register contains a reference to the object.
BytecodeArrayBuilder& Delete(Register object, LanguageMode language_mode);
......
......@@ -5646,34 +5646,60 @@ void BytecodeGenerator::VisitCallSuper(Call* expr) {
// Prepare the constructor to the super call.
Register this_function = VisitForRegisterValue(super->this_function_var());
Register constructor = register_allocator()->NewRegister();
builder()
->LoadAccumulatorWithRegister(this_function)
.GetSuperConstructor(constructor);
Register instance = register_allocator()->NewRegister();
BytecodeLabel super_ctor_call_done;
bool omit_super_ctor = FLAG_omit_default_ctors &&
IsDerivedConstructor(info()->literal()->kind());
if (spread_position == Call::kHasNonFinalSpread) {
// First generate the array containing all arguments.
BuildCreateArrayLiteral(args, nullptr);
RegisterList construct_args = register_allocator()->NewRegisterList(3);
builder()->StoreAccumulatorInRegister(construct_args[1]);
VisitForRegisterValue(super->new_target_var(), construct_args[2]);
if (omit_super_ctor) {
BuildSuperCallOptimization(this_function, construct_args[2],
construct_args[0], instance,
&super_ctor_call_done);
} else {
builder()
->LoadAccumulatorWithRegister(this_function)
.GetSuperConstructor(construct_args[0]);
}
// Check if the constructor is in fact a constructor.
builder()->ThrowIfNotSuperConstructor(constructor);
builder()->ThrowIfNotSuperConstructor(construct_args[0]);
// Now pass that array to %reflect_construct.
RegisterList construct_args = register_allocator()->NewRegisterList(3);
builder()->StoreAccumulatorInRegister(construct_args[1]);
builder()->MoveRegister(constructor, construct_args[0]);
VisitForRegisterValue(super->new_target_var(), construct_args[2]);
builder()->CallJSRuntime(Context::REFLECT_CONSTRUCT_INDEX, construct_args);
} else {
RegisterList args_regs = register_allocator()->NewGrowableRegisterList();
VisitArguments(args, &args_regs);
// The new target is loaded into the new_target register from the
// {new.target} variable.
Register new_target = register_allocator()->NewRegister();
VisitForRegisterValue(super->new_target_var(), new_target);
// Use the same register for storing the 'constructor' or the 'instance',
// they won't both be needed at the same time. If we jump to super_ctor_call
// done, only 'instance' is used, and if we don't, only 'constructor' is
// used.
Register& constructor = instance;
if (omit_super_ctor) {
BuildSuperCallOptimization(this_function, new_target, constructor,
instance, &super_ctor_call_done);
} else {
builder()
->LoadAccumulatorWithRegister(this_function)
.GetSuperConstructor(constructor);
}
// Check if the constructor is in fact a constructor.
builder()->ThrowIfNotSuperConstructor(constructor);
// The new target is loaded into the accumulator from the
// {new.target} variable.
VisitForAccumulatorValue(super->new_target_var());
builder()->LoadAccumulatorWithRegister(new_target);
builder()->SetExpressionPosition(expr);
int feedback_slot_index = feedback_index(feedback_spec()->AddCallICSlot());
......@@ -5693,6 +5719,8 @@ void BytecodeGenerator::VisitCallSuper(Call* expr) {
builder()->Construct(constructor, args_regs, feedback_slot_index);
}
}
builder()->StoreAccumulatorInRegister(instance);
builder()->Bind(&super_ctor_call_done);
// Explicit calls to the super constructor using super() perform an
// implicit binding assignment to the 'this' variable.
......@@ -5701,12 +5729,10 @@ void BytecodeGenerator::VisitCallSuper(Call* expr) {
// 'this' isn't accessed in default constructors.
if (!IsDefaultConstructor(info()->literal()->kind())) {
Variable* var = closure_scope()->GetReceiverScope()->receiver();
builder()->LoadAccumulatorWithRegister(instance);
BuildVariableAssignment(var, Token::INIT, HoleCheckMode::kRequired);
}
Register instance = register_allocator()->NewRegister();
builder()->StoreAccumulatorInRegister(instance);
// The constructor scope always needs ScopeInfo, so we are certain that
// the first constructor scope found in the outer scope chain is the
// scope that we are looking for for this super() call.
......@@ -5746,6 +5772,15 @@ void BytecodeGenerator::VisitCallSuper(Call* expr) {
builder()->LoadAccumulatorWithRegister(instance);
}
void BytecodeGenerator::BuildSuperCallOptimization(
Register this_function, Register new_target, Register constructor,
Register instance, BytecodeLabel* super_ctor_call_done) {
DCHECK(FLAG_omit_default_ctors);
builder()->FindNonDefaultConstructor(this_function, new_target, constructor,
instance);
builder()->JumpIfTrue(ToBooleanMode::kAlreadyBoolean, super_ctor_call_done);
}
void BytecodeGenerator::VisitCallNew(CallNew* expr) {
RegisterList args = register_allocator()->NewGrowableRegisterList();
......
......@@ -406,6 +406,10 @@ class BytecodeGenerator final : public AstVisitor<BytecodeGenerator> {
template <typename ExpressionFunc>
void BuildOptionalChain(ExpressionFunc expression_func);
void BuildSuperCallOptimization(Register this_function, Register new_target,
Register constructor, Register instance,
BytecodeLabel* super_ctor_call_done);
// Visitors for obtaining expression result in the accumulator, in a
// register, or just getting the effect. Some visitors return a TypeHint which
// specifies the type of the result of the visited expression.
......
......@@ -227,6 +227,9 @@ namespace interpreter {
\
/* GetSuperConstructor operator */ \
V(GetSuperConstructor, ImplicitRegisterUse::kReadAccumulator, \
OperandType::kRegOut) \
V(FindNonDefaultConstructor, ImplicitRegisterUse::kWriteAccumulator, \
OperandType::kReg, OperandType::kReg, OperandType::kRegOut, \
OperandType::kRegOut) \
\
/* Call operations */ \
......
......@@ -2781,6 +2781,100 @@ IGNITION_HANDLER(ThrowIfNotSuperConstructor, InterpreterAssembler) {
}
}
// FinNonDefaultConstructor <this_function> <new_target> <constructor>
// <instance>
//
// Walks the prototype chain from <this_function>'s super ctor until we see a
// non-default ctor. If the walk ends at a default base ctor, creates an
// instance and stores it in <instance> and stores true into the accumulator.
// Otherwise, stores the first non-default ctor into <constructor> and false
// into the accumulator.
IGNITION_HANDLER(FindNonDefaultConstructor, InterpreterAssembler) {
TNode<Context> context = GetContext();
TVARIABLE(Object, constructor);
Label loop(this, &constructor), found_default_base_ctor(this, &constructor),
found_something_else(this, &constructor);
TNode<JSFunction> this_function = CAST(LoadRegisterAtOperandIndex(0));
constructor = GetSuperConstructor(this_function);
// Disable the optimization if the debugger is active, so that we can still
// put breakpoints into default constructors.
GotoIf(IsDebugActive(), &found_something_else);
// Disable the optimization if the array iterator has been changed. V8 uses
// the array iterator for the spread in default ctors, even though it
// shouldn't, according to the spec. This ensures that omitting default ctors
// doesn't change the behavior. See crbug.com/v8/13249.
GotoIf(IsArrayIteratorProtectorCellInvalid(), &found_something_else);
TNode<Object> new_target = LoadRegisterAtOperandIndex(1);
Goto(&loop);
BIND(&loop);
{
// We know constructor can't be a SMI, since it's a prototype. If it's not a
// JSFunction, the error will be thrown by the ThrowIfNotSuperConstructor
// which follows this bytecode.
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);
// If there are private methods, bail out. TODO(v8:13091): Handle them here.
TNode<Context> function_context =
LoadJSFunctionContext(CAST(constructor.value()));
TNode<ScopeInfo> scope_info = LoadScopeInfo(function_context);
GotoIf(LoadScopeInfoClassScopeHasPrivateBrand(scope_info),
&found_something_else);
const TNode<Uint32T> function_kind =
LoadFunctionKind(CAST(constructor.value()));
// A default base ctor -> stop the search.
GotoIf(Word32Equal(
function_kind,
static_cast<uint32_t>(FunctionKind::kDefaultBaseConstructor)),
&found_default_base_ctor);
// Something else than a default derived ctor (e.g., a non-default base
// ctor, a non-default derived ctor, or a normal function) -> stop the
// search.
GotoIfNot(Word32Equal(function_kind,
static_cast<uint32_t>(
FunctionKind::kDefaultDerivedConstructor)),
&found_something_else);
constructor = GetSuperConstructor(CAST(constructor.value()));
Goto(&loop);
}
// We don't need to re-check the proctector, since the loop cannot call into
// user code. Even if GetSuperConstructor returns a Proxy, we will throw since
// it's not a constructor, and not invoke [[GetPrototypeOf]] on it.
// TODO(v8:13091): make sure this is still valid after we handle class fields.
BIND(&found_default_base_ctor);
{
// Create an object directly, without calling the default base ctor.
TNode<Object> instance = CallBuiltin(Builtin::kFastNewObject, context,
constructor.value(), new_target);
StoreRegisterAtOperandIndex(instance, 3);
SetAccumulator(TrueConstant());
Dispatch();
}
BIND(&found_something_else);
{
// Not a base ctor (or bailed out).
StoreRegisterAtOperandIndex(constructor.value(), 2);
SetAccumulator(FalseConstant());
Dispatch();
}
}
// Debugger
//
// Call runtime to handle debugger statement.
......
......@@ -1577,6 +1577,11 @@ void MaglevGraphBuilder::VisitGetSuperConstructor() {
StoreRegister(iterator_.GetRegisterOperand(0), map_proto);
}
void MaglevGraphBuilder::VisitFindNonDefaultConstructor() {
// TODO(v8:13091): Implement.
CHECK(false);
}
void MaglevGraphBuilder::InlineCallFromRegisters(
int argc_count, ConvertReceiverMode receiver_mode,
compiler::JSFunctionRef function) {
......
// 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 --no-sparkplug
// TODO(v8:13091): Enable sparkplug.
// 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 {}
assertEquals(0, iterationCount);
new C();
// C default ctor doing "...args" and B default ctor doing "...args".
assertEquals(2, iterationCount);
Array.prototype[Symbol.iterator] = oldIterator;
})();
// 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 --no-sparkplug
// TODO(v8:13091): Enable sparkplug.
(function OmitDefaultBaseCtor() {
class A {} // default base ctor -> will be omitted
class B extends A {}
const o = new B();
assertSame(B.prototype, o.__proto__);
})();
(function OmitDefaultDerivedCtor() {
class A { constructor() {} }
class B extends A {} // default derived ctor -> will be omitted
class C extends B {}
const o = new C();
assertSame(C.prototype, o.__proto__);
})();
(function OmitDefaultBaseAndDerivedCtor() {
class A {} // default base ctor -> will be omitted
class B extends A {} // default derived ctor -> will be omitted
class C extends B {}
const o = new C();
assertSame(C.prototype, o.__proto__);
})();
(function OmitDefaultBaseCtorWithExplicitSuper() {
class A {} // default base ctor -> will be omitted
class B extends A { constructor() { super(); } }
const o = new B();
assertSame(B.prototype, o.__proto__);
})();
(function OmitDefaultDerivedCtorWithExplicitSuper() {
class A { constructor() {} }
class B extends A {} // default derived ctor -> will be omitted
class C extends B { constructor() { super(); } }
const o = new C();
assertSame(C.prototype, o.__proto__);
})();
(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(); } }
const o = new C();
assertSame(C.prototype, o.__proto__);
})();
(function OmitDefaultBaseCtorWithExplicitSuperAndNonFinalSpread() {
class A {} // default base ctor -> will be omitted
class B extends A { constructor(...args) { super(1, ...args, 2); } }
const o = new B(3, 4);
assertSame(B.prototype, o.__proto__);
})();
(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); } }
const o = new C(3, 4);
assertSame(C.prototype, o.__proto__);
})();
(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); } }
const o = new C(3, 4);
assertSame(C.prototype, o.__proto__);
})();
(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 {}
const a = new A(1, 2, 3);
assertEquals(1, ctorCallCount);
assertEquals([1, 2, 3], lastArgs);
assertTrue(a.baseTagged);
// 'A' default ctor will be omitted.
class B1 extends A {}
const b1 = new B1(4, 5, 6);
assertEquals(2, ctorCallCount);
assertEquals([4, 5, 6], lastArgs);
assertTrue(b1.baseTagged);
// The same test with non-final spread; 'A' default ctor will be omitted.
class B2 extends A {
constructor(...args) { super(1, ...args, 2); }
}
const b2 = new B2(4, 5, 6);
assertEquals(3, ctorCallCount);
assertEquals([1, 4, 5, 6, 2], lastArgs);
assertTrue(b2.baseTagged);
})();
(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 {}
const a = new A(1, 2, 3);
assertEquals(1, ctorCallCount);
assertEquals([1, 2, 3], lastArgs);
assertTrue(a.derivedTagged);
// 'A' default ctor will be omitted.
class B1 extends A {}
const b1 = new B1(4, 5, 6);
assertEquals(2, ctorCallCount);
assertEquals([4, 5, 6], lastArgs);
assertTrue(b1.derivedTagged);
// The same test with non-final spread. 'A' default ctor will be omitted.
class B2 extends A {
constructor(...args) { super(1, ...args, 2); }
}
const b2 = new B2(4, 5, 6);
assertEquals(3, ctorCallCount);
assertEquals([1, 4, 5, 6, 2], lastArgs);
assertTrue(b2.derivedTagged);
})();
(function BaseFunctionCalled() {
let baseFunctionCallCount = 0;
function BaseFunction() {
++baseFunctionCallCount;
this.baseTagged = true;
}
class A1 extends BaseFunction {}
const a1 = new A1();
assertEquals(1, baseFunctionCallCount);
assertTrue(a1.baseTagged);
class A2 extends BaseFunction {
constructor(...args) { super(1, ...args, 2); }
}
const a2 = new A2();
assertEquals(2, baseFunctionCallCount);
assertTrue(a2.baseTagged);
})();
(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); }}
// 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); }}
// 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());
}
};
// Install an object which is not a constructor into the class hierarchy.
C.__proto__ = {};
let callCount = 0;
function foo() {
++callCount;
}
assertThrows(() => { new D1(); }, TypeError);
assertEquals(1, callCount);
assertThrows(() => { new D2(); }, TypeError);
assertEquals(2, 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;
function foo() {
C.__proto__ = A;
C.prototype.__proto__ = A.prototype;
++fooCallCount;
}
new D();
assertEquals(1, fooCallCount);
assertEquals(0, ctorCallCount);
})();
// 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;
function foo() {
C.__proto__ = A;
C.prototype.__proto__ = A.prototype;
++fooCallCount;
}
new D();
assertEquals(1, fooCallCount);
assertEquals(0, ctorCallCount);
})();
(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); }};
const b = new B();
assertTrue(b.isA());
const c1 = new C1();
assertTrue(c1.isA());
const c2 = new C2();
assertTrue(c2.isA());
})();
(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); }};
const c1 = new C1();
assertTrue(c1.isB());
const c2 = new C2();
assertTrue(c2.isB());
})();
(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); }};
const b = new B();
assertEquals('private', b.callPrivate());
const c1 = new C1();
assertEquals('private', c1.callPrivate());
const c2 = new C2();
assertEquals('private', c2.callPrivate());
})();
(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); }};
const c1 = new C1();
assertEquals('private', c1.callPrivate());
const c2 = new C2();
assertEquals('private', c2.callPrivate());
})();
(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); }};
const b = new B();
assertEquals('private', b.getPrivate());
const c1 = new C1();
assertEquals('private', c1.getPrivate());
const c2 = new C2();
assertEquals('private', c2.getPrivate());
})();
(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); }};
const c1 = new C1();
assertEquals('private', c1.getPrivate());
const c2 = new C2();
assertEquals('private', c2.getPrivate());
})();
(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); }};
const b = new B();
b.setPrivate();
assertEquals('private', b.secret);
const c1 = new C1();
c1.setPrivate();
assertEquals('private', c1.secret);
const c2 = new C2();
c2.setPrivate();
assertEquals('private', c2.secret);
})();
(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); }};
const c1 = new C1();
c1.setPrivate();
assertEquals('private', c1.secret);
const c2 = new C2();
c2.setPrivate();
assertEquals('private', c2.secret);
})();
(function BaseClassFields() {
class A {
aField = true;
};
class B extends A {};
class C1 extends B {};
class C2 extends B { constructor(...args) { super(1, ...args, 2); }};
const b = new B();
assertTrue(b.aField);
const c1 = new C1();
assertTrue(c1.aField);
const c2 = new C2();
assertTrue(c2.aField);
})();
(function DerivedClassFields() {
class A {};
class B extends A {
bField = true;
};
class C1 extends B {};
class C2 extends B { constructor(...args) { super(1, ...args, 2); }};
const c1 = new C1();
assertTrue(c1.bField);
const c2 = new C2();
assertTrue(c2.bField);
})();
......@@ -488,6 +488,9 @@ TEST_F(BytecodeArrayBuilderTest, AllBytecodesGenerated) {
// Type Information for DevTools is turned on.
scorecard[Bytecodes::ToByte(Bytecode::kCollectTypeProfile)] = 1;
// This bytecode is too inconvenient to test manually.
scorecard[Bytecodes::ToByte(Bytecode::kFindNonDefaultConstructor)] = 1;
// Check return occurs at the end and only once in the BytecodeArray.
CHECK_EQ(final_bytecode, Bytecode::kReturn);
CHECK_EQ(scorecard[Bytecodes::ToByte(final_bytecode)], 1);
......
......@@ -87,22 +87,22 @@ snippet: "
test = new B().constructor;
})();
"
frame size: 6
frame size: 5
parameter count: 1
bytecode array length: 39
bytecodes: [
B(Mov), R(closure), R(1),
/* 118 S> */ B(Ldar), R(1),
B(GetSuperConstructor), R(3),
B(LdaSmi), I8(1),
B(ThrowIfNotSuperConstructor), R(3),
/* 118 S> */ B(LdaSmi), I8(1),
B(Star4),
B(Ldar), R(1),
/* 118 E> */ B(GetSuperConstructor), R(3),
B(ThrowIfNotSuperConstructor), R(3),
B(Ldar), R(0),
/* 118 E> */ B(Construct), R(3), R(4), U8(1), U8(0),
B(Star5),
B(Star3),
B(Ldar), R(this),
B(ThrowSuperAlreadyCalledIfNotHole),
B(Mov), R(5), R(this),
B(Mov), R(3), R(this),
/* 128 S> */ B(Ldar), R(this),
B(ThrowSuperNotCalledIfHole),
B(LdaSmi), I8(2),
......@@ -130,20 +130,20 @@ snippet: "
test = new B().constructor;
})();
"
frame size: 5
frame size: 4
parameter count: 1
bytecode array length: 36
bytecodes: [
B(Mov), R(closure), R(1),
/* 117 S> */ B(Ldar), R(1),
B(GetSuperConstructor), R(3),
/* 117 E> */ B(GetSuperConstructor), R(3),
B(ThrowIfNotSuperConstructor), R(3),
B(Ldar), R(0),
/* 117 E> */ B(Construct), R(3), R(0), U8(0), U8(0),
B(Star4),
B(Star3),
B(Ldar), R(this),
B(ThrowSuperAlreadyCalledIfNotHole),
B(Mov), R(4), R(this),
B(Mov), R(3), R(this),
/* 126 S> */ B(Ldar), R(this),
B(ThrowSuperNotCalledIfHole),
B(LdaSmi), I8(2),
......
......@@ -159,32 +159,35 @@ snippet: "
"
frame size: 9
parameter count: 1
bytecode array length: 58
bytecode array length: 63
bytecodes: [
/* 89 S> */ B(LdaImmutableCurrentContextSlot), U8(4),
B(GetSuperConstructor), R(1),
B(ThrowIfNotSuperConstructor), R(1),
B(Star0),
B(LdaImmutableCurrentContextSlot), U8(3),
/* 89 E> */ B(Construct), R(1), R(0), U8(0), U8(0),
B(Star2),
B(Ldar), R(0),
/* 89 E> */ B(GetSuperConstructor), R(1),
B(ThrowIfNotSuperConstructor), R(1),
B(Ldar), R(2),
/* 89 E> */ B(Construct), R(1), R(0), U8(0), U8(0),
B(Star1),
B(LdaCurrentContextSlot), U8(2),
B(ThrowSuperAlreadyCalledIfNotHole),
B(Ldar), R(2),
B(Ldar), R(1),
B(StaCurrentContextSlot), U8(2),
B(LdaImmutableContextSlot), R(context), U8(3), U8(1),
B(Star4),
B(LdaSmi), I8(1),
B(Star6),
B(Mov), R(2), R(3),
B(Mov), R(1), R(3),
B(Mov), R(context), R(5),
B(CallRuntime), U16(Runtime::kAddPrivateBrand), R(3), U8(4),
B(GetNamedProperty), R(0), U8(0), U8(2),
B(JumpIfUndefined), U8(10),
B(Star8),
B(CallProperty0), R(8), R(2), U8(4),
B(Mov), R(2), R(7),
B(Ldar), R(2),
B(CallProperty0), R(8), R(1), U8(4),
B(Mov), R(1), R(7),
B(Ldar), R(1),
/* 96 S> */ B(Return),
]
constant pool: [
......@@ -208,32 +211,35 @@ snippet: "
"
frame size: 9
parameter count: 1
bytecode array length: 58
bytecode array length: 63
bytecodes: [
/* 88 S> */ B(LdaImmutableCurrentContextSlot), U8(4),
B(GetSuperConstructor), R(1),
B(ThrowIfNotSuperConstructor), R(1),
B(Star0),
B(LdaImmutableCurrentContextSlot), U8(3),
/* 88 E> */ B(Construct), R(1), R(0), U8(0), U8(0),
B(Star2),
B(Ldar), R(0),
/* 88 E> */ B(GetSuperConstructor), R(1),
B(ThrowIfNotSuperConstructor), R(1),
B(Ldar), R(2),
/* 88 E> */ B(Construct), R(1), R(0), U8(0), U8(0),
B(Star1),
B(LdaCurrentContextSlot), U8(2),
B(ThrowSuperAlreadyCalledIfNotHole),
B(Ldar), R(2),
B(Ldar), R(1),
B(StaCurrentContextSlot), U8(2),
B(LdaImmutableContextSlot), R(context), U8(3), U8(1),
B(Star4),
B(LdaSmi), I8(1),
B(Star6),
B(Mov), R(2), R(3),
B(Mov), R(1), R(3),
B(Mov), R(context), R(5),
B(CallRuntime), U16(Runtime::kAddPrivateBrand), R(3), U8(4),
B(GetNamedProperty), R(0), U8(0), U8(2),
B(JumpIfUndefined), U8(10),
B(Star8),
B(CallProperty0), R(8), R(2), U8(4),
B(Mov), R(2), R(7),
B(Ldar), R(2),
B(CallProperty0), R(8), R(1), U8(4),
B(Mov), R(1), R(7),
B(Ldar), R(1),
/* 95 S> */ B(Return),
]
constant pool: [
......@@ -256,32 +262,35 @@ snippet: "
"
frame size: 9
parameter count: 1
bytecode array length: 58
bytecode array length: 63
bytecodes: [
/* 88 S> */ B(LdaImmutableCurrentContextSlot), U8(4),
B(GetSuperConstructor), R(1),
B(ThrowIfNotSuperConstructor), R(1),
B(Star0),
B(LdaImmutableCurrentContextSlot), U8(3),
/* 88 E> */ B(Construct), R(1), R(0), U8(0), U8(0),
B(Star2),
B(Ldar), R(0),
/* 88 E> */ B(GetSuperConstructor), R(1),
B(ThrowIfNotSuperConstructor), R(1),
B(Ldar), R(2),
/* 88 E> */ B(Construct), R(1), R(0), U8(0), U8(0),
B(Star1),
B(LdaCurrentContextSlot), U8(2),
B(ThrowSuperAlreadyCalledIfNotHole),
B(Ldar), R(2),
B(Ldar), R(1),
B(StaCurrentContextSlot), U8(2),
B(LdaImmutableContextSlot), R(context), U8(3), U8(1),
B(Star4),
B(LdaSmi), I8(1),
B(Star6),
B(Mov), R(2), R(3),
B(Mov), R(1), R(3),
B(Mov), R(context), R(5),
B(CallRuntime), U16(Runtime::kAddPrivateBrand), R(3), U8(4),
B(GetNamedProperty), R(0), U8(0), U8(2),
B(JumpIfUndefined), U8(10),
B(Star8),
B(CallProperty0), R(8), R(2), U8(4),
B(Mov), R(2), R(7),
B(Ldar), R(2),
B(CallProperty0), R(8), R(1), U8(4),
B(Mov), R(1), R(7),
B(Ldar), R(1),
/* 95 S> */ B(Return),
]
constant pool: [
......
......@@ -25,7 +25,7 @@ bytecodes: [
B(Star2),
B(Mov), R(closure), R(1),
/* 93 S> */ B(Ldar), R(1),
B(GetSuperConstructor), R(4),
/* 93 E> */ B(GetSuperConstructor), R(4),
B(ThrowIfNotSuperConstructor), R(4),
B(Ldar), R(0),
/* 93 E> */ B(ConstructWithSpread), R(4), R(2), U8(1), U8(0),
......@@ -49,7 +49,7 @@ snippet: "
test = new B(1, 2, 3).constructor;
})();
"
frame size: 9
frame size: 8
parameter count: 1
bytecode array length: 38
bytecodes: [
......@@ -57,18 +57,18 @@ bytecodes: [
B(Star3),
B(Mov), R(closure), R(1),
B(Mov), R(3), R(2),
/* 140 S> */ B(Ldar), R(closure),
B(GetSuperConstructor), R(5),
B(LdaSmi), I8(1),
/* 140 S> */ B(LdaSmi), I8(1),
B(Star6),
/* 152 E> */ B(ThrowIfNotSuperConstructor), R(5),
B(Ldar), R(closure),
/* 140 E> */ B(GetSuperConstructor), R(5),
B(ThrowIfNotSuperConstructor), R(5),
B(Ldar), R(0),
B(Mov), R(3), R(7),
/* 140 E> */ B(ConstructWithSpread), R(5), R(6), U8(2), U8(0),
B(Star8),
B(Star5),
B(Ldar), R(this),
B(ThrowSuperAlreadyCalledIfNotHole),
B(Mov), R(8), R(this),
B(Mov), R(5), R(this),
B(Ldar), R(this),
B(ThrowSuperNotCalledIfHole),
/* 159 S> */ B(Return),
......@@ -93,15 +93,13 @@ snippet: "
"
frame size: 11
parameter count: 1
bytecode array length: 97
bytecode array length: 94
bytecodes: [
/* 128 E> */ B(CreateRestParameter),
B(Star3),
B(Mov), R(closure), R(1),
B(Mov), R(3), R(2),
/* 140 S> */ B(Ldar), R(closure),
B(GetSuperConstructor), R(5),
B(CreateArrayLiteral), U8(0), U8(0), U8(37),
/* 140 S> */ B(CreateArrayLiteral), U8(0), U8(0), U8(37),
B(Star7),
B(LdaSmi), I8(1),
/* 152 S> */ B(Star6),
......@@ -124,14 +122,15 @@ bytecodes: [
B(JumpLoop), U8(31), I8(0), U8(18),
B(LdaSmi), I8(1),
B(StaInArrayLiteral), R(7), R(6), U8(12),
B(ThrowIfNotSuperConstructor), R(5),
B(Mov), R(5), R(6),
B(Ldar), R(4),
/* 140 E> */ B(GetSuperConstructor), R(6),
B(ThrowIfNotSuperConstructor), R(6),
B(Mov), R(0), R(8),
/* 140 E> */ B(CallJSRuntime), U8(%reflect_construct), R(6), U8(3),
B(Star9),
B(CallJSRuntime), U8(%reflect_construct), R(6), U8(3),
B(Star5),
B(Ldar), R(this),
B(ThrowSuperAlreadyCalledIfNotHole),
B(Mov), R(9), R(this),
B(Mov), R(5), R(this),
B(Ldar), R(this),
B(ThrowSuperNotCalledIfHole),
/* 162 S> */ B(Return),
......
......@@ -77,10 +77,10 @@ INCOMPATIBLE_FLAGS_PER_VARIANT = {
"--liftoff-only", "--wasm-speculative-inlining",
"--wasm-dynamic-tiering"
],
"sparkplug": ["--jitless"],
"sparkplug": ["--jitless", "--no-sparkplug"],
"concurrent_sparkplug": ["--jitless"],
"maglev": ["--jitless"],
"always_sparkplug": ["--jitless"],
"always_sparkplug": ["--jitless", "--no-sparkplug"],
"code_serializer": [
"--cache=after-execute", "--cache=full-code-cache", "--cache=none"
],
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment