diff --git a/src/builtins/builtins-definitions.h b/src/builtins/builtins-definitions.h index 1819f899d0b6..652982718954 100644 --- a/src/builtins/builtins-definitions.h +++ b/src/builtins/builtins-definitions.h @@ -639,6 +639,7 @@ namespace internal { TFH(LoadGlobalICInsideTypeof, LoadGlobalWithVector) \ TFH(LoadGlobalICTrampoline, LoadGlobal) \ TFH(LoadGlobalICInsideTypeofTrampoline, LoadGlobal) \ + TFH(CloneObjectIC, CloneObjectWithVector) \ \ /* Map */ \ TFS(FindOrderedHashMapEntry, kTable, kKey) \ diff --git a/src/builtins/builtins-ic-gen.cc b/src/builtins/builtins-ic-gen.cc index 7ee50345aade..bbfabc7a0dd5 100644 --- a/src/builtins/builtins-ic-gen.cc +++ b/src/builtins/builtins-ic-gen.cc @@ -35,6 +35,7 @@ IC_BUILTIN(StoreICTrampoline) IC_BUILTIN(KeyedStoreIC) IC_BUILTIN(KeyedStoreICTrampoline) IC_BUILTIN(StoreInArrayLiteralIC) +IC_BUILTIN(CloneObjectIC) IC_BUILTIN_PARAM(LoadGlobalIC, LoadGlobalIC, NOT_INSIDE_TYPEOF) IC_BUILTIN_PARAM(LoadGlobalICInsideTypeof, LoadGlobalIC, INSIDE_TYPEOF) diff --git a/src/code-stub-assembler.cc b/src/code-stub-assembler.cc index b1330664ff73..2d7aff5c1b93 100644 --- a/src/code-stub-assembler.cc +++ b/src/code-stub-assembler.cc @@ -1989,6 +1989,13 @@ TNode CodeStubAssembler::LoadPropertyArrayElement( needs_poisoning)); } +TNode CodeStubAssembler::LoadPropertyArrayLength( + TNode object) { + TNode value = + LoadAndUntagObjectField(object, PropertyArray::kLengthAndHashOffset); + return Signed(DecodeWord(value)); +} + TNode CodeStubAssembler::LoadFixedTypedArrayBackingStore( TNode typed_array) { // Backing store = external_pointer + base_pointer. diff --git a/src/code-stub-assembler.h b/src/code-stub-assembler.h index 3d7859f0647b..06a331ac50a8 100644 --- a/src/code-stub-assembler.h +++ b/src/code-stub-assembler.h @@ -1002,6 +1002,7 @@ class V8_EXPORT_PRIVATE CodeStubAssembler : public compiler::CodeAssembler { TNode LoadPropertyArrayElement(SloppyTNode object, SloppyTNode index); + TNode LoadPropertyArrayLength(TNode object); // Load an array element from a FixedArray / WeakFixedArray, untag it and // return it as Word32. diff --git a/src/compiler/bytecode-graph-builder.cc b/src/compiler/bytecode-graph-builder.cc index b9f2233fe30f..4f87fe438f11 100644 --- a/src/compiler/bytecode-graph-builder.cc +++ b/src/compiler/bytecode-graph-builder.cc @@ -1625,6 +1625,18 @@ void BytecodeGraphBuilder::VisitCreateEmptyObjectLiteral() { environment()->BindAccumulator(literal); } +void BytecodeGraphBuilder::VisitCloneObject() { + PrepareEagerCheckpoint(); + Node* source = + environment()->LookupRegister(bytecode_iterator().GetRegisterOperand(0)); + int flags = bytecode_iterator().GetFlagOperand(1); + int slot = bytecode_iterator().GetIndexOperand(2); + const Operator* op = + javascript()->CloneObject(CreateVectorSlotPair(slot), flags); + Node* value = NewNode(op, source); + environment()->BindAccumulator(value, Environment::kAttachFrameState); +} + void BytecodeGraphBuilder::VisitGetTemplateObject() { Handle description( TemplateObjectDescription::cast( diff --git a/src/compiler/bytecode-graph-builder.h b/src/compiler/bytecode-graph-builder.h index a94a3d79afe7..3e1947faba7d 100644 --- a/src/compiler/bytecode-graph-builder.h +++ b/src/compiler/bytecode-graph-builder.h @@ -93,6 +93,12 @@ class BytecodeGraphBuilder { return MakeNode(op, arraysize(buffer), buffer, false); } + Node* NewNode(const Operator* op, Node* n1, Node* n2, Node* n3, Node* n4, + Node* n5, Node* n6) { + Node* buffer[] = {n1, n2, n3, n4, n5, n6}; + return MakeNode(op, arraysize(buffer), buffer, false); + } + // Helpers to create new control nodes. Node* NewIfTrue() { return NewNode(common()->IfTrue()); } Node* NewIfFalse() { return NewNode(common()->IfFalse()); } diff --git a/src/compiler/js-generic-lowering.cc b/src/compiler/js-generic-lowering.cc index 5e134307f423..b6a4045f00fb 100644 --- a/src/compiler/js-generic-lowering.cc +++ b/src/compiler/js-generic-lowering.cc @@ -532,6 +532,17 @@ void JSGenericLowering::LowerJSCreateLiteralObject(Node* node) { } } +void JSGenericLowering::LowerJSCloneObject(Node* node) { + CloneObjectParameters const& p = CloneObjectParametersOf(node->op()); + CallDescriptor::Flags flags = FrameStateFlagForCall(node); + Callable callable = + Builtins::CallableFor(isolate(), Builtins::kCloneObjectIC); + node->InsertInput(zone(), 1, jsgraph()->SmiConstant(p.flags())); + node->InsertInput(zone(), 2, jsgraph()->SmiConstant(p.feedback().index())); + node->InsertInput(zone(), 3, jsgraph()->HeapConstant(p.feedback().vector())); + ReplaceWithStubCall(node, callable, flags); +} + void JSGenericLowering::LowerJSCreateEmptyLiteralObject(Node* node) { UNREACHABLE(); // Eliminated in typed lowering. } diff --git a/src/compiler/js-operator.cc b/src/compiler/js-operator.cc index 5d45bb7f954e..a22d0d2ff975 100644 --- a/src/compiler/js-operator.cc +++ b/src/compiler/js-operator.cc @@ -534,6 +534,29 @@ const CreateLiteralParameters& CreateLiteralParametersOf(const Operator* op) { return OpParameter(op); } +bool operator==(CloneObjectParameters const& lhs, + CloneObjectParameters const& rhs) { + return lhs.feedback() == rhs.feedback() && lhs.flags() == rhs.flags(); +} + +bool operator!=(CloneObjectParameters const& lhs, + CloneObjectParameters const& rhs) { + return !(lhs == rhs); +} + +size_t hash_value(CloneObjectParameters const& p) { + return base::hash_combine(p.feedback(), p.flags()); +} + +std::ostream& operator<<(std::ostream& os, CloneObjectParameters const& p) { + return os << p.flags(); +} + +const CloneObjectParameters& CloneObjectParametersOf(const Operator* op) { + DCHECK(op->opcode() == IrOpcode::kJSCloneObject); + return OpParameter(op); +} + size_t hash_value(ForInMode mode) { return static_cast(mode); } std::ostream& operator<<(std::ostream& os, ForInMode mode) { @@ -1179,6 +1202,17 @@ const Operator* JSOperatorBuilder::CreateLiteralObject( parameters); // parameter } +const Operator* JSOperatorBuilder::CloneObject(VectorSlotPair const& feedback, + int literal_flags) { + CloneObjectParameters parameters(feedback, literal_flags); + return new (zone()) Operator1( // -- + IrOpcode::kJSCloneObject, // opcode + Operator::kNoProperties, // properties + "JSCloneObject", // name + 1, 1, 1, 1, 1, 1, // counts + parameters); // parameter +} + const Operator* JSOperatorBuilder::CreateEmptyLiteralObject() { return new (zone()) Operator( // -- IrOpcode::kJSCreateEmptyLiteralObject, // opcode diff --git a/src/compiler/js-operator.h b/src/compiler/js-operator.h index f73aca819f59..9881b42a20e5 100644 --- a/src/compiler/js-operator.h +++ b/src/compiler/js-operator.h @@ -626,6 +626,28 @@ std::ostream& operator<<(std::ostream&, CreateLiteralParameters const&); const CreateLiteralParameters& CreateLiteralParametersOf(const Operator* op); +class CloneObjectParameters final { + public: + CloneObjectParameters(VectorSlotPair const& feedback, int flags) + : feedback_(feedback), flags_(flags) {} + + VectorSlotPair const& feedback() const { return feedback_; } + int flags() const { return flags_; } + + private: + VectorSlotPair const feedback_; + int const flags_; +}; + +bool operator==(CloneObjectParameters const&, CloneObjectParameters const&); +bool operator!=(CloneObjectParameters const&, CloneObjectParameters const&); + +size_t hash_value(CloneObjectParameters const&); + +std::ostream& operator<<(std::ostream&, CloneObjectParameters const&); + +const CloneObjectParameters& CloneObjectParametersOf(const Operator* op); + // Descriptor used by the JSForInPrepare and JSForInNext opcodes. enum class ForInMode : uint8_t { kUseEnumCacheKeysAndIndices, @@ -716,6 +738,8 @@ class V8_EXPORT_PRIVATE JSOperatorBuilder final Handle constant, VectorSlotPair const& feedback, int literal_flags, int number_of_properties); + const Operator* CloneObject(VectorSlotPair const& feedback, + int literal_flags); const Operator* CreateLiteralRegExp(Handle constant_pattern, VectorSlotPair const& feedback, int literal_flags); diff --git a/src/compiler/opcodes.h b/src/compiler/opcodes.h index 7a6b19cb35d4..5637a5c03908 100644 --- a/src/compiler/opcodes.h +++ b/src/compiler/opcodes.h @@ -152,6 +152,7 @@ V(JSCreateEmptyLiteralArray) \ V(JSCreateLiteralObject) \ V(JSCreateEmptyLiteralObject) \ + V(JSCloneObject) \ V(JSCreateLiteralRegExp) #define JS_OBJECT_OP_LIST(V) \ diff --git a/src/compiler/operator-properties.cc b/src/compiler/operator-properties.cc index 689561059cf3..093c0e242cea 100644 --- a/src/compiler/operator-properties.cc +++ b/src/compiler/operator-properties.cc @@ -76,6 +76,7 @@ bool OperatorProperties::HasFrameStateInput(const Operator* op) { case IrOpcode::kJSCreateLiteralObject: case IrOpcode::kJSCreateLiteralRegExp: case IrOpcode::kJSCreateObject: + case IrOpcode::kJSCloneObject: // Property access operations case IrOpcode::kJSLoadNamed: diff --git a/src/compiler/typer.cc b/src/compiler/typer.cc index 0698b1daf01e..d78423e2ce0f 100644 --- a/src/compiler/typer.cc +++ b/src/compiler/typer.cc @@ -1214,6 +1214,10 @@ Type Typer::Visitor::TypeJSCreateEmptyLiteralObject(Node* node) { return Type::OtherObject(); } +Type Typer::Visitor::TypeJSCloneObject(Node* node) { + return Type::OtherObject(); +} + Type Typer::Visitor::TypeJSCreateLiteralRegExp(Node* node) { return Type::OtherObject(); } diff --git a/src/compiler/verifier.cc b/src/compiler/verifier.cc index 52cbd6e0b72d..6b2399f93fe6 100644 --- a/src/compiler/verifier.cc +++ b/src/compiler/verifier.cc @@ -716,6 +716,7 @@ void Verifier::Visitor::Check(Node* node, const AllNodes& all) { break; case IrOpcode::kJSCreateLiteralObject: case IrOpcode::kJSCreateEmptyLiteralObject: + case IrOpcode::kJSCloneObject: case IrOpcode::kJSCreateLiteralRegExp: // Type is OtherObject. CheckTypeIs(node, Type::OtherObject()); diff --git a/src/feedback-vector-inl.h b/src/feedback-vector-inl.h index c3c0c8c63d3e..1d3028cdaf04 100644 --- a/src/feedback-vector-inl.h +++ b/src/feedback-vector-inl.h @@ -67,6 +67,7 @@ int FeedbackMetadata::GetSlotSize(FeedbackSlotKind kind) { return 1; case FeedbackSlotKind::kCall: + case FeedbackSlotKind::kCloneObject: case FeedbackSlotKind::kLoadProperty: case FeedbackSlotKind::kLoadGlobalInsideTypeof: case FeedbackSlotKind::kLoadGlobalNotInsideTypeof: @@ -337,6 +338,7 @@ void FeedbackVector::ComputeCounts(int* with_type_info, int* generic, } case FeedbackSlotKind::kCreateClosure: case FeedbackSlotKind::kLiteral: + case FeedbackSlotKind::kCloneObject: break; case FeedbackSlotKind::kInvalid: case FeedbackSlotKind::kKindsNumber: diff --git a/src/feedback-vector.cc b/src/feedback-vector.cc index ba3b711b1efd..80e87a218f68 100644 --- a/src/feedback-vector.cc +++ b/src/feedback-vector.cc @@ -170,6 +170,8 @@ const char* FeedbackMetadata::Kind2String(FeedbackSlotKind kind) { return "ForIn"; case FeedbackSlotKind::kInstanceOf: return "InstanceOf"; + case FeedbackSlotKind::kCloneObject: + return "CloneObject"; case FeedbackSlotKind::kKindsNumber: break; } @@ -254,6 +256,7 @@ Handle FeedbackVector::New(Isolate* isolate, vector->set(index, *uninitialized_sentinel, SKIP_WRITE_BARRIER); extra_value = Smi::kZero; break; + case FeedbackSlotKind::kCloneObject: case FeedbackSlotKind::kLoadProperty: case FeedbackSlotKind::kLoadKeyed: case FeedbackSlotKind::kStoreNamedSloppy: @@ -416,6 +419,7 @@ void FeedbackNexus::ConfigureUninitialized() { SKIP_WRITE_BARRIER); break; } + case FeedbackSlotKind::kCloneObject: case FeedbackSlotKind::kCall: { SetFeedback(*FeedbackVector::UninitializedSentinel(isolate), SKIP_WRITE_BARRIER); @@ -480,6 +484,7 @@ bool FeedbackNexus::Clear() { case FeedbackSlotKind::kCall: case FeedbackSlotKind::kInstanceOf: case FeedbackSlotKind::kStoreDataPropertyInLiteral: + case FeedbackSlotKind::kCloneObject: if (!IsCleared()) { ConfigureUninitialized(); feedback_updated = true; @@ -501,6 +506,20 @@ void FeedbackNexus::ConfigurePremonomorphic() { SKIP_WRITE_BARRIER); } +bool FeedbackNexus::ConfigureMegamorphic() { + DisallowHeapAllocation no_gc; + Isolate* isolate = GetIsolate(); + MaybeObject* sentinel = + MaybeObject::FromObject(*FeedbackVector::MegamorphicSentinel(isolate)); + if (GetFeedback() != sentinel) { + SetFeedback(sentinel, SKIP_WRITE_BARRIER); + SetFeedbackExtra(HeapObjectReference::ClearedValue()); + return true; + } + + return false; +} + bool FeedbackNexus::ConfigureMegamorphic(IcCheckType property_type) { DisallowHeapAllocation no_gc; Isolate* isolate = GetIsolate(); @@ -661,6 +680,23 @@ InlineCacheState FeedbackNexus::StateFromFeedback() const { return MONOMORPHIC; } + case FeedbackSlotKind::kCloneObject: { + if (feedback == MaybeObject::FromObject( + *FeedbackVector::UninitializedSentinel(isolate))) { + return UNINITIALIZED; + } + if (feedback == MaybeObject::FromObject( + *FeedbackVector::MegamorphicSentinel(isolate))) { + return MEGAMORPHIC; + } + if (feedback->IsWeakOrClearedHeapObject()) { + return MONOMORPHIC; + } + + DCHECK(feedback->ToStrongHeapObject()->IsWeakFixedArray()); + return POLYMORPHIC; + } + case FeedbackSlotKind::kInvalid: case FeedbackSlotKind::kKindsNumber: UNREACHABLE(); @@ -703,6 +739,78 @@ void FeedbackNexus::ConfigureHandlerMode(const MaybeObjectHandle& handler) { SetFeedbackExtra(*handler); } +void FeedbackNexus::ConfigureCloneObject(Handle source_map, + Handle result_map) { + Isolate* isolate = GetIsolate(); + MaybeObject* maybe_feedback = GetFeedback(); + Handle feedback(maybe_feedback->IsStrongOrWeakHeapObject() + ? maybe_feedback->GetHeapObject() + : nullptr, + isolate); + switch (ic_state()) { + case UNINITIALIZED: + // Cache the first map seen which meets the fast case requirements. + SetFeedback(HeapObjectReference::Weak(*source_map)); + SetFeedbackExtra(*result_map); + break; + case MONOMORPHIC: + if (maybe_feedback->IsClearedWeakHeapObject()) { + // Remain in MONOMORPHIC state if previous feedback has been collected. + SetFeedback(HeapObjectReference::Weak(*source_map)); + SetFeedbackExtra(*result_map); + } else { + // Transition to POLYMORPHIC. + DCHECK_NE(*source_map, *feedback); + Handle array = + EnsureArrayOfSize(2 * kCloneObjectPolymorphicEntrySize); + array->Set(0, maybe_feedback); + array->Set(1, GetFeedbackExtra()); + array->Set(2, HeapObjectReference::Weak(*source_map)); + array->Set(3, MaybeObject::FromObject(*result_map)); + SetFeedbackExtra(HeapObjectReference::ClearedValue()); + } + break; + case POLYMORPHIC: { + static constexpr int kMaxElements = + IC::kMaxPolymorphicMapCount * kCloneObjectPolymorphicEntrySize; + Handle array = Handle::cast(feedback); + int i = 0; + for (; i < array->length(); i += kCloneObjectPolymorphicEntrySize) { + if (array->Get(i)->IsClearedWeakHeapObject()) { + break; + } + DCHECK_NE(array->Get(i)->GetHeapObject(), *source_map); + } + + if (i >= array->length()) { + if (i == kMaxElements) { + // Transition to MEGAMORPHIC. + MaybeObject* sentinel = MaybeObject::FromObject( + *FeedbackVector::MegamorphicSentinel(isolate)); + SetFeedback(sentinel, SKIP_WRITE_BARRIER); + SetFeedbackExtra(HeapObjectReference::ClearedValue()); + break; + } + + // Grow polymorphic feedback array. + Handle new_array = EnsureArrayOfSize( + array->length() + kCloneObjectPolymorphicEntrySize); + for (int j = 0; j < array->length(); ++j) { + new_array->Set(j, array->Get(j)); + } + array = new_array; + } + + array->Set(i, HeapObjectReference::Weak(*source_map)); + array->Set(i + 1, MaybeObject::FromObject(*result_map)); + break; + } + + default: + UNREACHABLE(); + } +} + int FeedbackNexus::GetCallCount() { DCHECK(IsCallICKind(kind())); diff --git a/src/feedback-vector.h b/src/feedback-vector.h index 880e4713d469..5aa655ee501f 100644 --- a/src/feedback-vector.h +++ b/src/feedback-vector.h @@ -51,6 +51,7 @@ enum class FeedbackSlotKind { kLiteral, kForIn, kInstanceOf, + kCloneObject, kKindsNumber // Last value indicating number of kinds. }; @@ -107,6 +108,10 @@ inline bool IsTypeProfileKind(FeedbackSlotKind kind) { return kind == FeedbackSlotKind::kTypeProfile; } +inline bool IsCloneObjectKind(FeedbackSlotKind kind) { + return kind == FeedbackSlotKind::kCloneObject; +} + inline TypeofMode GetTypeofModeFromSlotKind(FeedbackSlotKind kind) { DCHECK(IsLoadGlobalICKind(kind)); return (kind == FeedbackSlotKind::kLoadGlobalInsideTypeof) @@ -398,6 +403,10 @@ class V8_EXPORT_PRIVATE FeedbackVectorSpec { FeedbackSlot AddTypeProfileSlot(); + FeedbackSlot AddCloneObjectSlot() { + return AddSlot(FeedbackSlotKind::kCloneObject); + } + #ifdef OBJECT_PRINT // For gdb debugging. void Print(); @@ -602,6 +611,9 @@ class FeedbackNexus final { bool Clear(); void ConfigureUninitialized(); void ConfigurePremonomorphic(); + // ConfigureMegamorphic() returns true if the state of the underlying vector + // was changed. Extra feedback is cleared if the 0 parameter version is used. + bool ConfigureMegamorphic(); bool ConfigureMegamorphic(IcCheckType property_type); inline MaybeObject* GetFeedback() const; @@ -654,6 +666,10 @@ class FeedbackNexus final { int context_slot_index); void ConfigureHandlerMode(const MaybeObjectHandle& handler); + // For CloneObject ICs + static constexpr int kCloneObjectPolymorphicEntrySize = 2; + void ConfigureCloneObject(Handle source_map, Handle result_map); + // Bit positions in a smi that encodes lexical environment variable access. #define LEXICAL_MODE_BIT_FIELDS(V, _) \ V(ContextIndexBits, unsigned, 12, _) \ @@ -676,7 +692,6 @@ class FeedbackNexus final { std::vector GetSourcePositions() const; std::vector> GetTypesForSourcePositions(uint32_t pos) const; - protected: inline void SetFeedback(Object* feedback, WriteBarrierMode mode = UPDATE_WRITE_BARRIER); inline void SetFeedback(MaybeObject* feedback, diff --git a/src/ic/accessor-assembler.cc b/src/ic/accessor-assembler.cc index 7ffadfc84e80..97e3d2b72399 100644 --- a/src/ic/accessor-assembler.cc +++ b/src/ic/accessor-assembler.cc @@ -3391,5 +3391,128 @@ void AccessorAssembler::GenerateStoreInArrayLiteralIC() { StoreInArrayLiteralIC(&p); } +void AccessorAssembler::GenerateCloneObjectIC() { + typedef CloneObjectWithVectorDescriptor Descriptor; + Node* source = Parameter(Descriptor::kSource); + Node* flags = Parameter(Descriptor::kFlags); + Node* slot = Parameter(Descriptor::kSlot); + Node* vector = Parameter(Descriptor::kVector); + Node* context = Parameter(Descriptor::kContext); + TVARIABLE(MaybeObject, var_handler); + Label if_handler(this, &var_handler); + Label miss(this, Label::kDeferred), try_polymorphic(this, Label::kDeferred), + try_megamorphic(this, Label::kDeferred); + + CSA_SLOW_ASSERT(this, TaggedIsNotSmi(source)); + Node* source_map = LoadMap(UncheckedCast(source)); + GotoIf(IsDeprecatedMap(source_map), &miss); + TNode feedback = TryMonomorphicCase( + slot, vector, source_map, &if_handler, &var_handler, &try_polymorphic); + + BIND(&if_handler); + { + Comment("CloneObjectIC_if_handler"); + + // Handlers for the CloneObjectIC stub are weak references to the Map of + // a result object. + TNode result_map = CAST(var_handler.value()); + TVARIABLE(Object, var_properties, EmptyFixedArrayConstant()); + TVARIABLE(FixedArrayBase, var_elements, EmptyFixedArrayConstant()); + + Label allocate_object(this); + GotoIf(IsNullOrUndefined(source), &allocate_object); + CSA_SLOW_ASSERT(this, IsJSObjectMap(result_map)); + + // The IC fast case should only be taken if the result map a compatible + // elements kind with the source object. + TNode source_elements = LoadElements(source); + + auto flags = ExtractFixedArrayFlag::kAllFixedArraysDontCopyCOW; + var_elements = CAST(CloneFixedArray(source_elements, flags)); + + // Copy the PropertyArray backing store. The source PropertyArray must be + // either an Smi, or a PropertyArray. + // FIXME: Make a CSA macro for this + TNode source_properties = + LoadObjectField(source, JSObject::kPropertiesOrHashOffset); + { + GotoIf(TaggedIsSmi(source_properties), &allocate_object); + GotoIf(IsEmptyFixedArray(source_properties), &allocate_object); + + // This IC requires that the source object has fast properties + CSA_SLOW_ASSERT(this, IsPropertyArray(CAST(source_properties))); + TNode length = LoadPropertyArrayLength( + UncheckedCast(source_properties)); + GotoIf(IntPtrEqual(length, IntPtrConstant(0)), &allocate_object); + + auto mode = INTPTR_PARAMETERS; + var_properties = CAST(AllocatePropertyArray(length, mode)); + CopyPropertyArrayValues(source_properties, var_properties.value(), length, + SKIP_WRITE_BARRIER, mode); + } + + Goto(&allocate_object); + BIND(&allocate_object); + TNode object = UncheckedCast(AllocateJSObjectFromMap( + result_map, var_properties.value(), var_elements.value())); + ReturnIf(IsNullOrUndefined(source), object); + + // Lastly, clone any in-object properties. + // Determine the inobject property capacity of both objects, and copy the + // smaller number into the resulting object. + Node* source_start = LoadMapInobjectPropertiesStartInWords(source_map); + Node* source_size = LoadMapInstanceSizeInWords(source_map); + Node* result_start = LoadMapInobjectPropertiesStartInWords(result_map); + Node* field_offset_difference = + TimesPointerSize(IntPtrSub(result_start, source_start)); + BuildFastLoop(source_start, source_size, + [=](Node* field_index) { + Node* field_offset = TimesPointerSize(field_index); + Node* field = LoadObjectField(source, field_offset); + Node* result_offset = + IntPtrAdd(field_offset, field_offset_difference); + StoreObjectFieldNoWriteBarrier(object, result_offset, + field); + }, + 1, INTPTR_PARAMETERS, IndexAdvanceMode::kPost); + Return(object); + } + + BIND(&try_polymorphic); + TNode strong_feedback = ToStrongHeapObject(feedback, &miss); + { + Comment("CloneObjectIC_try_polymorphic"); + GotoIfNot(IsWeakFixedArrayMap(LoadMap(strong_feedback)), &try_megamorphic); + HandlePolymorphicCase(source_map, CAST(strong_feedback), &if_handler, + &var_handler, &miss, 2); + } + + BIND(&try_megamorphic); + { + Comment("CloneObjectIC_try_megamorphic"); + CSA_ASSERT( + this, + Word32Or(WordEqual(strong_feedback, + LoadRoot(Heap::kuninitialized_symbolRootIndex)), + WordEqual(strong_feedback, + LoadRoot(Heap::kmegamorphic_symbolRootIndex)))); + GotoIfNot(WordEqual(strong_feedback, + LoadRoot(Heap::kmegamorphic_symbolRootIndex)), + &miss); + TailCallRuntime(Runtime::kCloneObjectIC_Slow, context, source, flags); + } + + BIND(&miss); + { + Comment("CloneObjectIC_miss"); + Node* map_or_result = CallRuntime(Runtime::kCloneObjectIC_Miss, context, + source, flags, slot, vector); + var_handler = UncheckedCast(map_or_result); + GotoIf(IsMap(map_or_result), &if_handler); + CSA_ASSERT(this, IsJSObject(map_or_result)); + Return(map_or_result); + } +} + } // namespace internal } // namespace v8 diff --git a/src/ic/accessor-assembler.h b/src/ic/accessor-assembler.h index 0aa9f0ab41c3..170046c9fd75 100644 --- a/src/ic/accessor-assembler.h +++ b/src/ic/accessor-assembler.h @@ -39,6 +39,7 @@ class AccessorAssembler : public CodeStubAssembler { void GenerateStoreICTrampoline(); void GenerateStoreGlobalIC(); void GenerateStoreGlobalICTrampoline(); + void GenerateCloneObjectIC(); void GenerateLoadGlobalIC(TypeofMode typeof_mode); void GenerateLoadGlobalICTrampoline(TypeofMode typeof_mode); diff --git a/src/ic/ic.cc b/src/ic/ic.cc index 9d28d8ab9fb9..bc835a4c89b3 100644 --- a/src/ic/ic.cc +++ b/src/ic/ic.cc @@ -8,6 +8,7 @@ #include "src/api-arguments-inl.h" #include "src/api.h" #include "src/arguments.h" +#include "src/ast/ast.h" #include "src/base/bits.h" #include "src/conversions.h" #include "src/execution.h" @@ -2446,6 +2447,132 @@ RUNTIME_FUNCTION(Runtime_ElementsTransitionAndStoreIC_Miss) { } } +static bool CanFastCloneObject(Handle map) { + DisallowHeapAllocation no_gc; + if (map->IsNullOrUndefinedMap()) return true; + if (!map->IsJSObjectMap() || + !IsSmiOrObjectElementsKind(map->elements_kind()) || + !map->OnlyHasSimpleProperties()) { + return false; + } + + DescriptorArray* descriptors = map->instance_descriptors(); + for (int i = 0; i < map->NumberOfOwnDescriptors(); i++) { + PropertyDetails details = descriptors->GetDetails(i); + Name* key = descriptors->GetKey(i); + if (details.kind() != kData || !details.IsEnumerable() || + key->IsPrivateField()) { + return false; + } + } + + return true; +} + +static Handle FastCloneObjectMap(Isolate* isolate, + Handle source, int flags) { + Handle source_map(source->map(), isolate); + SLOW_DCHECK(source->IsNullOrUndefined() || CanFastCloneObject(source_map)); + Handle constructor(isolate->native_context()->object_function(), + isolate); + DCHECK(constructor->has_initial_map()); + Handle initial_map(constructor->initial_map(), isolate); + Handle map = initial_map; + + if (source_map->IsJSObjectMap() && source_map->GetInObjectProperties() != + initial_map->GetInObjectProperties()) { + int inobject_properties = source_map->GetInObjectProperties(); + int instance_size = + JSObject::kHeaderSize + kPointerSize * inobject_properties; + int unused = source_map->UnusedInObjectProperties(); + DCHECK(instance_size <= JSObject::kMaxInstanceSize); + map = Map::CopyInitialMap(isolate, map, instance_size, inobject_properties, + unused); + } + + if (flags & ObjectLiteral::kHasNullPrototype) { + if (map.is_identical_to(initial_map)) { + map = Map::Copy(isolate, map, "ObjectWithNullProto"); + } + Map::SetPrototype(isolate, map, isolate->factory()->null_value()); + } + + if (source->IsNullOrUndefined() || !source_map->NumberOfOwnDescriptors()) { + return map; + } + + if (map.is_identical_to(initial_map)) { + map = Map::Copy(isolate, map, "InitializeClonedDescriptors"); + } + + Handle source_descriptors(source_map->instance_descriptors(), + isolate); + int size = source_map->NumberOfOwnDescriptors(); + int slack = 0; + Handle descriptors = DescriptorArray::CopyForFastObjectClone( + isolate, source_descriptors, size, slack); + Handle layout = + LayoutDescriptor::New(isolate, map, descriptors, size); + map->InitializeDescriptors(*descriptors, *layout); + map->CopyUnusedPropertyFields(*source_map); + + return map; +} + +static MaybeHandle CloneObjectSlowPath(Isolate* isolate, + Handle source, + int flags) { + Handle new_object; + if (flags & ObjectLiteral::kHasNullPrototype) { + new_object = isolate->factory()->NewJSObjectWithNullProto(); + } else { + Handle constructor(isolate->native_context()->object_function(), + isolate); + new_object = isolate->factory()->NewJSObject(constructor); + } + + if (source->IsNullOrUndefined()) { + return new_object; + } + + MAYBE_RETURN(JSReceiver::SetOrCopyDataProperties(isolate, new_object, source, + nullptr, false), + MaybeHandle()); + return new_object; +} + +RUNTIME_FUNCTION(Runtime_CloneObjectIC_Miss) { + HandleScope scope(isolate); + DCHECK_EQ(4, args.length()); + Handle source = args.at(0); + int flags = args.smi_at(1); + FeedbackSlot slot = FeedbackVector::ToSlot(args.smi_at(2)); + Handle vector = args.at(3); + + FeedbackNexus nexus(vector, slot); + Handle source_map(source->map(), isolate); + + if (!CanFastCloneObject(source_map) || nexus.IsMegamorphic()) { + // Migrate to slow mode if needed. + nexus.ConfigureMegamorphic(); + RETURN_RESULT_OR_FAILURE(isolate, + CloneObjectSlowPath(isolate, source, flags)); + } + + Handle result_map = FastCloneObjectMap(isolate, source, flags); + nexus.ConfigureCloneObject(source_map, result_map); + + return *result_map; +} + +RUNTIME_FUNCTION(Runtime_CloneObjectIC_Slow) { + HandleScope scope(isolate); + DCHECK_EQ(2, args.length()); + Handle source = args.at(0); + int flags = args.smi_at(1); + RETURN_RESULT_OR_FAILURE(isolate, + CloneObjectSlowPath(isolate, source, flags)); +} RUNTIME_FUNCTION(Runtime_StoreCallbackProperty) { Handle receiver = args.at(0); diff --git a/src/interface-descriptors.cc b/src/interface-descriptors.cc index 85c2b56e21b0..2d8b0edd7ef9 100644 --- a/src/interface-descriptors.cc +++ b/src/interface-descriptors.cc @@ -291,5 +291,10 @@ void WasmGrowMemoryDescriptor::InitializePlatformSpecific( DefaultInitializePlatformSpecific(data, kParameterCount); } +void CloneObjectWithVectorDescriptor::InitializePlatformSpecific( + CallInterfaceDescriptorData* data) { + DefaultInitializePlatformSpecific(data, kParameterCount); +} + } // namespace internal } // namespace v8 diff --git a/src/interface-descriptors.h b/src/interface-descriptors.h index cf841383b290..e2ad08ddfbda 100644 --- a/src/interface-descriptors.h +++ b/src/interface-descriptors.h @@ -74,6 +74,7 @@ namespace internal { V(FrameDropperTrampoline) \ V(RunMicrotasks) \ V(WasmGrowMemory) \ + V(CloneObjectWithVector) \ BUILTIN_LIST_TFS(V) class V8_EXPORT_PRIVATE CallInterfaceDescriptorData { @@ -1021,6 +1022,17 @@ class WasmGrowMemoryDescriptor final : public CallInterfaceDescriptor { DECLARE_DESCRIPTOR(WasmGrowMemoryDescriptor, CallInterfaceDescriptor) }; +class CloneObjectWithVectorDescriptor final : public CallInterfaceDescriptor { + public: + DEFINE_PARAMETERS(kSource, kFlags, kSlot, kVector) + DEFINE_RESULT_AND_PARAMETER_TYPES(MachineType::TaggedPointer(), // result 1 + MachineType::AnyTagged(), // kSource + MachineType::TaggedSigned(), // kFlags + MachineType::TaggedSigned(), // kSlot + MachineType::AnyTagged()) // kVector + DECLARE_DESCRIPTOR(CloneObjectWithVectorDescriptor, CallInterfaceDescriptor) +}; + #define DEFINE_TFS_BUILTIN_DESCRIPTOR(Name, ...) \ class Name##Descriptor : public CallInterfaceDescriptor { \ public: \ diff --git a/src/interpreter/bytecode-array-builder.cc b/src/interpreter/bytecode-array-builder.cc index 9559ff5cc96d..d042c8f0f24e 100644 --- a/src/interpreter/bytecode-array-builder.cc +++ b/src/interpreter/bytecode-array-builder.cc @@ -986,6 +986,13 @@ BytecodeArrayBuilder& BytecodeArrayBuilder::CreateEmptyObjectLiteral() { return *this; } +BytecodeArrayBuilder& BytecodeArrayBuilder::CloneObject(Register source, + int flags, + int feedback_slot) { + OutputCloneObject(source, flags, feedback_slot); + return *this; +} + BytecodeArrayBuilder& BytecodeArrayBuilder::GetTemplateObject( size_t template_object_description_entry, int feedback_slot) { OutputGetTemplateObject(template_object_description_entry, feedback_slot); diff --git a/src/interpreter/bytecode-array-builder.h b/src/interpreter/bytecode-array-builder.h index f34f6f3a7d85..3feda904959b 100644 --- a/src/interpreter/bytecode-array-builder.h +++ b/src/interpreter/bytecode-array-builder.h @@ -238,6 +238,8 @@ class V8_EXPORT_PRIVATE BytecodeArrayBuilder final { int literal_index, int flags, Register output); BytecodeArrayBuilder& CreateEmptyObjectLiteral(); + BytecodeArrayBuilder& CloneObject(Register source, int flags, + int feedback_slot); // Gets or creates the template for a TemplateObjectDescription which will // be inserted at constant pool index |template_object_description_entry|. diff --git a/src/interpreter/bytecode-generator.cc b/src/interpreter/bytecode-generator.cc index 79792a3d5618..6bcb5df682ef 100644 --- a/src/interpreter/bytecode-generator.cc +++ b/src/interpreter/bytecode-generator.cc @@ -2108,42 +2108,70 @@ void BytecodeGenerator::VisitObjectLiteral(ObjectLiteral* expr) { return; } - int literal_index = feedback_index(feedback_spec()->AddLiteralSlot()); // Deep-copy the literal boilerplate. uint8_t flags = CreateObjectLiteralFlags::Encode( expr->ComputeFlags(), expr->IsFastCloningSupported()); Register literal = register_allocator()->NewRegister(); - size_t entry; - // If constant properties is an empty fixed array, use a cached empty fixed - // array to ensure it's only added to the constant pool once. - if (expr->properties_count() == 0) { - entry = builder()->EmptyObjectBoilerplateDescriptionConstantPoolEntry(); + + // Create literal object. + int property_index = 0; + bool is_spread = + expr->properties()->first()->kind() == ObjectLiteral::Property::SPREAD; + if (is_spread) { + // Avoid the slow path for spreads in the following common cases: + // 1) `let obj = { ...source }` + // 2) `let obj = { ...source, override: 1 }` + // 3) `let obj = { ...source, ...overrides }` + RegisterAllocationScope register_scope(this); + Expression* property = expr->properties()->first()->value(); + Register from_value = VisitForRegisterValue(property); + + BytecodeLabels clone_object(zone()); + builder()->JumpIfUndefined(clone_object.New()); + builder()->JumpIfNull(clone_object.New()); + builder()->ToObject(from_value); + + clone_object.Bind(builder()); + int clone_index = feedback_index(feedback_spec()->AddCloneObjectSlot()); + builder()->CloneObject(from_value, flags, clone_index); + builder()->StoreAccumulatorInRegister(literal); + property_index++; + + // FIXME: incorporate compile-time constants following the initial spread + // into the CloneObject opcode, to be included in the final value. } else { - entry = builder()->AllocateDeferredConstantPoolEntry(); - object_literals_.push_back(std::make_pair(expr, entry)); + size_t entry; + // If constant properties is an empty fixed array, use a cached empty fixed + // array to ensure it's only added to the constant pool once. + if (expr->properties_count() == 0) { + entry = builder()->EmptyObjectBoilerplateDescriptionConstantPoolEntry(); + } else { + entry = builder()->AllocateDeferredConstantPoolEntry(); + object_literals_.push_back(std::make_pair(expr, entry)); + } + // TODO(cbruni): Directly generate runtime call for literals we cannot + // optimize once the CreateShallowObjectLiteral stub is in sync with the TF + // optimizations. + int literal_index = feedback_index(feedback_spec()->AddLiteralSlot()); + builder()->CreateObjectLiteral(entry, literal_index, flags, literal); } - // TODO(cbruni): Directly generate runtime call for literals we cannot - // optimize once the CreateShallowObjectLiteral stub is in sync with the TF - // optimizations. - builder()->CreateObjectLiteral(entry, literal_index, flags, literal); // Store computed values into the literal. - int property_index = 0; AccessorTable accessor_table(zone()); for (; property_index < expr->properties()->length(); property_index++) { ObjectLiteral::Property* property = expr->properties()->at(property_index); if (property->is_computed_name()) break; - if (property->IsCompileTimeValue()) continue; + if (!is_spread && property->IsCompileTimeValue()) continue; RegisterAllocationScope inner_register_scope(this); Literal* key = property->key()->AsLiteral(); switch (property->kind()) { case ObjectLiteral::Property::SPREAD: - case ObjectLiteral::Property::CONSTANT: UNREACHABLE(); + case ObjectLiteral::Property::CONSTANT: case ObjectLiteral::Property::MATERIALIZED_LITERAL: - DCHECK(!property->value()->IsCompileTimeValue()); + DCHECK(is_spread || !property->value()->IsCompileTimeValue()); V8_FALLTHROUGH; case ObjectLiteral::Property::COMPUTED: { // It is safe to use [[Put]] here because the boilerplate already diff --git a/src/interpreter/bytecodes.h b/src/interpreter/bytecodes.h index b1ae88c1baf8..0e543877f7d3 100644 --- a/src/interpreter/bytecodes.h +++ b/src/interpreter/bytecodes.h @@ -251,6 +251,8 @@ namespace interpreter { V(CreateObjectLiteral, AccumulatorUse::kNone, OperandType::kIdx, \ OperandType::kIdx, OperandType::kFlag8, OperandType::kRegOut) \ V(CreateEmptyObjectLiteral, AccumulatorUse::kWrite) \ + V(CloneObject, AccumulatorUse::kWrite, OperandType::kReg, \ + OperandType::kFlag8, OperandType::kIdx) \ \ /* Tagged templates */ \ V(GetTemplateObject, AccumulatorUse::kWrite, OperandType::kIdx, \ diff --git a/src/interpreter/interpreter-generator.cc b/src/interpreter/interpreter-generator.cc index 7b0a4f5eca35..6372439918e3 100644 --- a/src/interpreter/interpreter-generator.cc +++ b/src/interpreter/interpreter-generator.cc @@ -2437,6 +2437,26 @@ IGNITION_HANDLER(CreateEmptyObjectLiteral, InterpreterAssembler) { Dispatch(); } +// CloneObject +// +// Allocates a new JSObject with each enumerable own property copied from +// {source}, converting getters into data properties. +IGNITION_HANDLER(CloneObject, InterpreterAssembler) { + Node* source = LoadRegisterAtOperandIndex(0); + Node* bytecode_flags = BytecodeOperandFlag(1); + Node* raw_flags = + DecodeWordFromWord32(bytecode_flags); + Node* smi_flags = SmiTag(raw_flags); + Node* raw_slot = BytecodeOperandIdx(2); + Node* smi_slot = SmiTag(raw_slot); + Node* feedback_vector = LoadFeedbackVector(); + Node* context = GetContext(); + Node* result = CallBuiltin(Builtins::kCloneObjectIC, context, source, + smi_flags, smi_slot, feedback_vector); + SetAccumulator(result); + Dispatch(); +} + // GetTemplateObject // // Creates the template to pass for tagged templates and returns it in the diff --git a/src/objects-printer.cc b/src/objects-printer.cc index a939efc3b5ef..3726a851c31b 100644 --- a/src/objects-printer.cc +++ b/src/objects-printer.cc @@ -1096,7 +1096,8 @@ void FeedbackNexus::Print(std::ostream& os) { // NOLINT case FeedbackSlotKind::kInstanceOf: case FeedbackSlotKind::kStoreDataPropertyInLiteral: case FeedbackSlotKind::kStoreKeyedStrict: - case FeedbackSlotKind::kStoreInArrayLiteral: { + case FeedbackSlotKind::kStoreInArrayLiteral: + case FeedbackSlotKind::kCloneObject: { os << ICState2String(StateFromFeedback()); break; } diff --git a/src/objects.cc b/src/objects.cc index f86f722a4fd9..67168ee0077a 100644 --- a/src/objects.cc +++ b/src/objects.cc @@ -10179,6 +10179,38 @@ Handle DescriptorArray::CopyUpToAddAttributes( return descriptors; } +// Create a new descriptor array with only enumerable, configurable, writeable +// data properties, but identical field locations. +Handle DescriptorArray::CopyForFastObjectClone( + Isolate* isolate, Handle src, int enumeration_index, + int slack) { + if (enumeration_index + slack == 0) { + return isolate->factory()->empty_descriptor_array(); + } + + int size = enumeration_index; + Handle descriptors = + DescriptorArray::Allocate(isolate, size, slack); + + for (int i = 0; i < size; ++i) { + Name* key = src->GetKey(i); + PropertyDetails details = src->GetDetails(i); + + SLOW_DCHECK(!key->IsPrivateField() && details.IsEnumerable() && + details.kind() == kData); + + // Ensure the ObjectClone property details are NONE, and that all source + // details did not contain DONT_ENUM. + PropertyDetails new_details( + kData, NONE, details.location(), kDefaultFieldConstness, + details.representation(), details.field_index()); + descriptors->Set(i, key, src->GetValue(i), new_details); + } + + descriptors->Sort(); + + return descriptors; +} bool DescriptorArray::IsEqualUpTo(DescriptorArray* desc, int nof_descriptors) { for (int i = 0; i < nof_descriptors; i++) { diff --git a/src/objects/descriptor-array.h b/src/objects/descriptor-array.h index c77e1000b7dc..c24deb68ad9c 100644 --- a/src/objects/descriptor-array.h +++ b/src/objects/descriptor-array.h @@ -107,6 +107,10 @@ class DescriptorArray : public WeakFixedArray { Isolate* isolate, Handle desc, int enumeration_index, PropertyAttributes attributes, int slack = 0); + static Handle CopyForFastObjectClone( + Isolate* isolate, Handle desc, int enumeration_index, + int slack = 0); + // Sort the instance descriptors by the hash codes of their keys. void Sort(); diff --git a/src/objects/map-inl.h b/src/objects/map-inl.h index 59214bd18fb6..b75f7be0b7ac 100644 --- a/src/objects/map-inl.h +++ b/src/objects/map-inl.h @@ -298,6 +298,17 @@ int Map::UnusedPropertyFields() const { return unused; } +int Map::UnusedInObjectProperties() const { + // Like Map::UnusedPropertyFields(), but returns 0 for out of object + // properties. + int value = used_or_unused_instance_size_in_words(); + DCHECK_IMPLIES(!IsJSObjectMap(), value == 0); + if (value >= JSObject::kFieldsAdded) { + return instance_size_in_words() - value; + } + return 0; +} + int Map::used_or_unused_instance_size_in_words() const { return RELAXED_READ_BYTE_FIELD(this, kUsedOrUnusedInstanceSizeInWordsOffset); } @@ -503,6 +514,17 @@ bool Map::CanTransition() const { bool Map::IsBooleanMap() const { return this == GetReadOnlyRoots().boolean_map(); } + +bool Map::IsNullMap() const { return this == GetReadOnlyRoots().null_map(); } + +bool Map::IsUndefinedMap() const { + return this == GetReadOnlyRoots().undefined_map(); +} + +bool Map::IsNullOrUndefinedMap() const { + return IsNullMap() || IsUndefinedMap(); +} + bool Map::IsPrimitiveMap() const { return instance_type() <= LAST_PRIMITIVE_TYPE; } diff --git a/src/objects/map.h b/src/objects/map.h index 97a290b82428..37780f7f0344 100644 --- a/src/objects/map.h +++ b/src/objects/map.h @@ -212,6 +212,8 @@ class Map : public HeapObject { // Tells how many unused property fields (in-object or out-of object) are // available in the instance (only used for JSObject in fast mode). inline int UnusedPropertyFields() const; + // Tells how many unused in-object property words are present. + inline int UnusedInObjectProperties() const; // Updates the counters tracking unused fields in the object. inline void SetInObjectUnusedPropertyFields(int unused_property_fields); // Updates the counters tracking unused fields in the property array. @@ -759,6 +761,9 @@ class Map : public HeapObject { inline bool CanTransition() const; inline bool IsBooleanMap() const; + inline bool IsNullMap() const; + inline bool IsUndefinedMap() const; + inline bool IsNullOrUndefinedMap() const; inline bool IsPrimitiveMap() const; inline bool IsJSReceiverMap() const; inline bool IsJSObjectMap() const; diff --git a/src/runtime/runtime.h b/src/runtime/runtime.h index accb97d0e690..1a96d1dac34a 100644 --- a/src/runtime/runtime.h +++ b/src/runtime/runtime.h @@ -588,7 +588,9 @@ namespace internal { F(StoreGlobalIC_Slow, 5, 1) \ F(StoreIC_Miss, 5, 1) \ F(StoreInArrayLiteralIC_Slow, 5, 1) \ - F(StorePropertyWithInterceptor, 5, 1) + F(StorePropertyWithInterceptor, 5, 1) \ + F(CloneObjectIC_Miss, 4, 1) \ + F(CloneObjectIC_Slow, 2, 1) #define FOR_EACH_INTRINSIC_RETURN_OBJECT(F) \ FOR_EACH_INTRINSIC_ARRAY(F) \ diff --git a/test/mjsunit/es9/object-spread-ic.js b/test/mjsunit/es9/object-spread-ic.js new file mode 100644 index 000000000000..d76ffd4eb80d --- /dev/null +++ b/test/mjsunit/es9/object-spread-ic.js @@ -0,0 +1,101 @@ +// Copyright 2018 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. + +(function testDoubleElements() { + function f(src) { return {...src}; } + var src = [1.5]; + src[0] = 1; + + // Uninitialized + assertEquals({ 0: 1 }, f(src)); + + src[0] = 1.3; + + // Monomorphic + assertEquals({ 0: 1.3 }, f(src)); +})(); + +(function testInObjectProperties() { + function f(src) { return {...src}; } + function C() { this.foo = "foo"; } + var src; + for (var i = 0; i < 10; ++i) { + src = new C(); + } + + // Uninitialized + assertEquals({ foo: "foo" }, f(src)); + + // Monomorphic + assertEquals({ foo: "foo" }, f(src)); +})(); + +(function testInObjectProperties2() { + function f(src) { return {...src}; } + function C() { + this.foo = "foo"; + this.p0 = "0"; + this.p1 = "1"; + this.p2 = "2"; + this.p3 = "3"; + } + var src; + for (var i = 0; i < 10; ++i) { + src = new C(); + } + + // Uninitialized + assertEquals({ foo: "foo", p0: "0", p1: "1", p2: "2", p3: "3" }, f(src)); + + // Monomorphic + assertEquals({ foo: "foo", p0: "0", p1: "1", p2: "2", p3: "3" }, f(src)); +})(); + +(function testPolymorphicToMegamorphic() { + function f(src) { return {...src}; } + function C1() { + this.foo = "foo"; + this.p0 = "0"; + this.p1 = "1"; + this.p2 = "2"; + this.p3 = "3"; + } + function C2() { + this.p0 = "0"; + this.p1 = "1"; + this[0] = 0; + } + function C3() { + this.x = 774; + this.y = 663; + this.rgb = 0xFF00FF; + } + function C4() { + this.qqq = {}; + this.v_1 = []; + this.name = "C4"; + this.constructor = C4; + } + + // Uninitialized + assertEquals({ foo: "foo", p0: "0", p1: "1", p2: "2", p3: "3" }, f(new C1())); + + // Monomorphic + assertEquals({ foo: "foo", p0: "0", p1: "1", p2: "2", p3: "3" }, f(new C1())); + + // Polymorphic (2) + assertEquals({ 0: 0, p0: "0", p1: "1" }, f(new C2())); + assertEquals({ 0: 0, p0: "0", p1: "1" }, f(new C2())); + + // Polymorphic (3) + assertEquals({ x: 774, y: 663, rgb: 0xFF00FF }, f(new C3())); + assertEquals({ x: 774, y: 663, rgb: 0xFF00FF }, f(new C3())); + + // Polymorphic (4) + assertEquals({ qqq: {}, v_1: [], name: "C4", constructor: C4 }, f(new C4())); + assertEquals({ qqq: {}, v_1: [], name: "C4", constructor: C4 }, f(new C4())); + + // Megamorphic + assertEquals({ boop: 1 }, f({ boop: 1 })); +})(); diff --git a/test/unittests/interpreter/bytecode-array-builder-unittest.cc b/test/unittests/interpreter/bytecode-array-builder-unittest.cc index 1ae636ecebba..5030d3897dfa 100644 --- a/test/unittests/interpreter/bytecode-array-builder-unittest.cc +++ b/test/unittests/interpreter/bytecode-array-builder-unittest.cc @@ -376,7 +376,8 @@ TEST_F(BytecodeArrayBuilderTest, AllBytecodesGenerated) { .CreateArrayLiteral(0, 0, 0) .CreateEmptyArrayLiteral(0) .CreateObjectLiteral(0, 0, 0, reg) - .CreateEmptyObjectLiteral(); + .CreateEmptyObjectLiteral() + .CloneObject(reg, 0, 0); // Emit load and store operations for module variables. builder.LoadModuleVariable(-1, 42)