From 35841b505570a14793c64f86c5fe7c57542dd8b5 Mon Sep 17 00:00:00 2001 From: ishell Date: Mon, 16 Feb 2015 07:25:33 -0800 Subject: [PATCH] Property reconfiguring implemented. Previous approach for property reconfiguration was to create a free-floating map with generalized representations of all fields. This patch does it right. When property is reconfigured either by changing its kind (kData <-> kAccessor) or its attributes it implies creation of a new branch in transition tree. If such a branch already existed before reconfiguration then it should be merged with the old (or source) branch of the transition tree. Merging procedure includes all the heavy machinery such as property location changes (kDescriptor -> kField), field representation/field type generalization, map deprecation, etc. Review URL: https://codereview.chromium.org/888623002 Cr-Commit-Position: refs/heads/master@{#26667} --- src/lookup.cc | 6 +- src/objects-inl.h | 6 +- src/objects-printer.cc | 1 + src/objects.cc | 679 +++++++++----- src/objects.h | 43 +- src/property-details.h | 9 + src/property.cc | 4 +- test/cctest/cctest.gyp | 1 + test/cctest/test-migrations.cc | 2017 ++++++++++++++++++++++++++++++++++++++++ 9 files changed, 2508 insertions(+), 258 deletions(-) create mode 100644 test/cctest/test-migrations.cc diff --git a/src/lookup.cc b/src/lookup.cc index 22da70c..672c026 100644 --- a/src/lookup.cc +++ b/src/lookup.cc @@ -105,8 +105,10 @@ void LookupIterator::ReconfigureDataProperty(Handle value, PropertyDetails details(attributes, v8::internal::DATA, 0); JSObject::SetNormalizedProperty(holder, name(), value, details); } else { - holder_map_ = Map::ReconfigureDataProperty(holder_map_, descriptor_number(), - attributes); + holder_map_ = Map::ReconfigureExistingProperty( + holder_map_, descriptor_number(), i::kData, attributes); + holder_map_ = + Map::PrepareForDataProperty(holder_map_, descriptor_number(), value); JSObject::MigrateToMap(holder, holder_map_); } diff --git a/src/objects-inl.h b/src/objects-inl.h index 6c504fb..0c72d95 100644 --- a/src/objects-inl.h +++ b/src/objects-inl.h @@ -58,7 +58,7 @@ PropertyDetails PropertyDetails::AsDeleted() const { int PropertyDetails::field_width_in_words() const { - DCHECK(type() == DATA); + DCHECK(location() == kField); if (!FLAG_unbox_double_fields) return 1; if (kDoubleSize == kPointerSize) return 1; return representation().IsDouble() ? kDoubleSize / kPointerSize : 1; @@ -3148,13 +3148,13 @@ PropertyType DescriptorArray::GetType(int descriptor_number) { int DescriptorArray::GetFieldIndex(int descriptor_number) { - DCHECK(GetDetails(descriptor_number).type() == DATA); + DCHECK(GetDetails(descriptor_number).location() == kField); return GetDetails(descriptor_number).field_index(); } HeapType* DescriptorArray::GetFieldType(int descriptor_number) { - DCHECK(GetDetails(descriptor_number).type() == DATA); + DCHECK(GetDetails(descriptor_number).location() == kField); return HeapType::cast(GetValue(descriptor_number)); } diff --git a/src/objects-printer.cc b/src/objects-printer.cc index 357d7b8..87f0396 100644 --- a/src/objects-printer.cc +++ b/src/objects-printer.cc @@ -424,6 +424,7 @@ void Map::MapPrint(std::ostream& os) { // NOLINT if (has_instance_call_handler()) os << " - instance_call_handler\n"; if (is_access_check_needed()) os << " - access_check_needed\n"; if (!is_extensible()) os << " - non-extensible\n"; + if (is_observed()) os << " - observed\n"; os << " - back pointer: " << Brief(GetBackPointer()); os << "\n - instance descriptors " << (owns_descriptors() ? "(own) " : "") << "#" << NumberOfOwnDescriptors() << ": " diff --git a/src/objects.cc b/src/objects.cc index 425d770..775a664 100644 --- a/src/objects.cc +++ b/src/objects.cc @@ -1269,6 +1269,25 @@ void JSObject::PrintElementsTransition( } +void Map::PrintReconfiguration(FILE* file, int modify_index, PropertyKind kind, + PropertyAttributes attributes) { + OFStream os(file); + os << "[reconfiguring "; + constructor_name()->PrintOn(file); + os << "] "; + Name* name = instance_descriptors()->GetKey(modify_index); + if (name->IsString()) { + String::cast(name)->PrintOn(file); + } else { + os << "{symbol " << static_cast(name) << "}"; + } + os << ": " << (kind == kData ? "kData" : "ACCESSORS") << ", attrs: "; + os << attributes << " ["; + JavaScriptFrame::PrintTop(GetIsolate(), file, false, true); + os << "]\n"; +} + + void Map::PrintGeneralization(FILE* file, const char* reason, int modify_index, @@ -1977,15 +1996,19 @@ void JSObject::MigrateFastToFast(Handle object, Handle new_map) { PropertyDetails details = new_descriptors->GetDetails(i); if (details.type() != DATA) continue; PropertyDetails old_details = old_descriptors->GetDetails(i); - if (old_details.type() == ACCESSOR_CONSTANT) { - DCHECK(details.representation().IsTagged()); - continue; - } Representation old_representation = old_details.representation(); Representation representation = details.representation(); - DCHECK(old_details.type() == DATA_CONSTANT || old_details.type() == DATA); Handle value; - if (old_details.type() == DATA_CONSTANT) { + if (old_details.type() == ACCESSOR_CONSTANT) { + // In case of kAccessor -> kData property reconfiguration, the property + // must already be prepared for data or certain type. + DCHECK(!details.representation().IsNone()); + if (details.representation().IsDouble()) { + value = isolate->factory()->NewHeapNumber(0, MUTABLE); + } else { + value = isolate->factory()->uninitialized_value(); + } + } else if (old_details.type() == DATA_CONSTANT) { value = handle(old_descriptors->GetValue(i), isolate); DCHECK(!old_representation.IsDouble() && !representation.IsDouble()); } else { @@ -2079,17 +2102,15 @@ int Map::NumberOfFields() { DescriptorArray* descriptors = instance_descriptors(); int result = 0; for (int i = 0; i < NumberOfOwnDescriptors(); i++) { - if (descriptors->GetDetails(i).type() == DATA) result++; + if (descriptors->GetDetails(i).location() == kField) result++; } return result; } -Handle Map::CopyGeneralizeAllRepresentations(Handle map, - int modify_index, - StoreMode store_mode, - PropertyAttributes attributes, - const char* reason) { +Handle Map::CopyGeneralizeAllRepresentations( + Handle map, int modify_index, StoreMode store_mode, PropertyKind kind, + PropertyAttributes attributes, const char* reason) { Isolate* isolate = map->GetIsolate(); Handle old_descriptors(map->instance_descriptors(), isolate); int number_of_own_descriptors = map->NumberOfOwnDescriptors(); @@ -2110,53 +2131,43 @@ Handle Map::CopyGeneralizeAllRepresentations(Handle map, MaybeHandle(), reason, SPECIAL_TRANSITION); // Unless the instance is being migrated, ensure that modify_index is a field. - PropertyDetails details = descriptors->GetDetails(modify_index); - if (store_mode == FORCE_FIELD && - (details.type() != DATA || details.attributes() != attributes)) { - int field_index = details.type() == DATA ? details.field_index() - : new_map->NumberOfFields(); - DataDescriptor d(handle(descriptors->GetKey(modify_index), isolate), - field_index, attributes, Representation::Tagged()); - descriptors->Replace(modify_index, &d); - if (details.type() != DATA) { - int unused_property_fields = new_map->unused_property_fields() - 1; - if (unused_property_fields < 0) { - unused_property_fields += JSObject::kFieldsAdded; + if (modify_index >= 0) { + PropertyDetails details = descriptors->GetDetails(modify_index); + if (store_mode == FORCE_FIELD && + (details.type() != DATA || details.attributes() != attributes)) { + int field_index = details.type() == DATA ? details.field_index() + : new_map->NumberOfFields(); + DataDescriptor d(handle(descriptors->GetKey(modify_index), isolate), + field_index, attributes, Representation::Tagged()); + descriptors->Replace(modify_index, &d); + if (details.type() != DATA) { + int unused_property_fields = new_map->unused_property_fields() - 1; + if (unused_property_fields < 0) { + unused_property_fields += JSObject::kFieldsAdded; + } + new_map->set_unused_property_fields(unused_property_fields); } - new_map->set_unused_property_fields(unused_property_fields); + } else { + DCHECK(details.attributes() == attributes); } - } else { - DCHECK(details.attributes() == attributes); - } - if (FLAG_trace_generalization) { - HeapType* field_type = - (details.type() == DATA) - ? map->instance_descriptors()->GetFieldType(modify_index) - : NULL; - map->PrintGeneralization( - stdout, reason, modify_index, new_map->NumberOfOwnDescriptors(), - new_map->NumberOfOwnDescriptors(), - details.type() == DATA_CONSTANT && store_mode == FORCE_FIELD, - details.representation(), Representation::Tagged(), field_type, - HeapType::Any()); + if (FLAG_trace_generalization) { + HeapType* field_type = + (details.type() == DATA) + ? map->instance_descriptors()->GetFieldType(modify_index) + : NULL; + map->PrintGeneralization( + stdout, reason, modify_index, new_map->NumberOfOwnDescriptors(), + new_map->NumberOfOwnDescriptors(), + details.type() == DATA_CONSTANT && store_mode == FORCE_FIELD, + details.representation(), Representation::Tagged(), field_type, + HeapType::Any()); + } } return new_map; } -// static -Handle Map::CopyGeneralizeAllRepresentations(Handle map, - int modify_index, - StoreMode store_mode, - const char* reason) { - PropertyDetails details = - map->instance_descriptors()->GetDetails(modify_index); - return CopyGeneralizeAllRepresentations(map, modify_index, store_mode, - details.attributes(), reason); -} - - void Map::DeprecateTransitionTree() { if (is_deprecated()) return; if (HasTransitionArray()) { @@ -2172,6 +2183,13 @@ void Map::DeprecateTransitionTree() { } +static inline bool EqualImmutableValues(Object* obj1, Object* obj2) { + if (obj1 == obj2) return true; // Valid for both kData and kAccessor kinds. + // TODO(ishell): compare AccessorPairs. + return false; +} + + // Invalidates a transition target at |key|, and installs |new_descriptors| over // the current instance_descriptors to ensure proper sharing of descriptor // arrays. @@ -2242,16 +2260,22 @@ Map* Map::FindLastMatchMap(int verbatim, DescriptorArray* next_descriptors = next->instance_descriptors(); PropertyDetails next_details = next_descriptors->GetDetails(i); - if (details.type() != next_details.type()) break; - if (details.attributes() != next_details.attributes()) break; + DCHECK_EQ(details.kind(), next_details.kind()); + DCHECK_EQ(details.attributes(), next_details.attributes()); + if (details.location() != next_details.location()) break; if (!details.representation().Equals(next_details.representation())) break; - if (next_details.type() == DATA) { - if (!descriptors->GetFieldType(i)->NowIs( - next_descriptors->GetFieldType(i))) break; + + if (next_details.location() == kField) { + HeapType* next_field_type = next_descriptors->GetFieldType(i); + if (!descriptors->GetFieldType(i)->NowIs(next_field_type)) { + break; + } } else { - if (descriptors->GetValue(i) != next_descriptors->GetValue(i)) break; + if (!EqualImmutableValues(descriptors->GetValue(i), + next_descriptors->GetValue(i))) { + break; + } } - current = next; } return current; @@ -2367,16 +2391,42 @@ void Map::GeneralizeFieldType(Handle map, int modify_index, } -// Generalize the representation of the descriptor at |modify_index|. -// This method rewrites the transition tree to reflect the new change. To avoid -// high degrees over polymorphism, and to stabilize quickly, on every rewrite -// the new type is deduced by merging the current type with any potential new -// (partial) version of the type in the transition tree. +static inline Handle GetFieldType(Isolate* isolate, + Handle descriptors, + int descriptor, + PropertyLocation location, + Representation representation) { +#ifdef DEBUG + PropertyDetails details = descriptors->GetDetails(descriptor); + DCHECK_EQ(kData, details.kind()); + DCHECK_EQ(details.location(), location); +#endif + if (location == kField) { + return handle(descriptors->GetFieldType(descriptor), isolate); + } else { + return descriptors->GetValue(descriptor) + ->OptimalType(isolate, representation); + } +} + + +// Reconfigures property at |modify_index| with |new_kind|, |new_attributes|, +// |store_mode| and/or |new_representation|/|new_field_type|. +// If |modify_index| is negative then no properties are reconfigured but the +// map is migrated to the up-to-date non-deprecated state. +// +// This method rewrites or completes the transition tree to reflect the new +// change. To avoid high degrees over polymorphism, and to stabilize quickly, +// on every rewrite the new type is deduced by merging the current type with +// any potential new (partial) version of the type in the transition tree. // To do this, on each rewrite: // - Search the root of the transition tree using FindRootMap. -// - Find |target_map|, the newest matching version of this map using the keys -// in the |old_map|'s descriptor array to walk the transition tree. -// - Merge/generalize the descriptor array of the |old_map| and |target_map|. +// - Find |target_map|, the newest matching version of this map using the +// virtually "enhanced" |old_map|'s descriptor array (i.e. whose entry at +// |modify_index| is considered to be of |new_kind| and having +// |new_attributes|) to walk the transition tree. +// - Merge/generalize the "enhanced" descriptor array of the |old_map| and +// descriptor array of the |target_map|. // - Generalize the |modify_index| descriptor using |new_representation| and // |new_field_type|. // - Walk the tree again starting from the root towards |target_map|. Stop at @@ -2386,68 +2436,113 @@ void Map::GeneralizeFieldType(Handle map, int modify_index, // Return it. // - Otherwise, invalidate the outdated transition target from |target_map|, and // replace its transition tree with a new branch for the updated descriptors. -Handle Map::GeneralizeRepresentation(Handle old_map, - int modify_index, - Representation new_representation, - Handle new_field_type, - StoreMode store_mode) { +Handle Map::ReconfigureProperty(Handle old_map, int modify_index, + PropertyKind new_kind, + PropertyAttributes new_attributes, + Representation new_representation, + Handle new_field_type, + StoreMode store_mode) { + DCHECK_NE(kAccessor, new_kind); // TODO(ishell): not supported yet. + DCHECK(store_mode != FORCE_FIELD || modify_index >= 0); Isolate* isolate = old_map->GetIsolate(); Handle old_descriptors( old_map->instance_descriptors(), isolate); int old_nof = old_map->NumberOfOwnDescriptors(); - PropertyDetails old_details = old_descriptors->GetDetails(modify_index); - Representation old_representation = old_details.representation(); - - // It's fine to transition from None to anything but double without any - // modification to the object, because the default uninitialized value for - // representation None can be overwritten by both smi and tagged values. - // Doubles, however, would require a box allocation. - if (old_representation.IsNone() && !new_representation.IsNone() && + + // If it's just a representation generalization case (i.e. property kind and + // attributes stays unchanged) it's fine to transition from None to anything + // but double without any modification to the object, because the default + // uninitialized value for representation None can be overwritten by both + // smi and tagged values. Doubles, however, would require a box allocation. + if (modify_index >= 0 && !new_representation.IsNone() && !new_representation.IsDouble()) { - DCHECK(old_details.type() == DATA); - if (FLAG_trace_generalization) { - old_map->PrintGeneralization( - stdout, "uninitialized field", - modify_index, old_map->NumberOfOwnDescriptors(), - old_map->NumberOfOwnDescriptors(), false, - old_representation, new_representation, - old_descriptors->GetFieldType(modify_index), *new_field_type); - } - Handle field_owner(old_map->FindFieldOwner(modify_index), isolate); + PropertyDetails old_details = old_descriptors->GetDetails(modify_index); + Representation old_representation = old_details.representation(); + + if (old_representation.IsNone()) { + DCHECK_EQ(new_kind, old_details.kind()); + DCHECK_EQ(new_attributes, old_details.attributes()); + DCHECK_EQ(DATA, old_details.type()); + if (FLAG_trace_generalization) { + old_map->PrintGeneralization( + stdout, "uninitialized field", modify_index, + old_map->NumberOfOwnDescriptors(), + old_map->NumberOfOwnDescriptors(), false, old_representation, + new_representation, old_descriptors->GetFieldType(modify_index), + *new_field_type); + } + Handle field_owner(old_map->FindFieldOwner(modify_index), isolate); - GeneralizeFieldType(field_owner, modify_index, new_representation, - new_field_type); - DCHECK(old_descriptors->GetDetails(modify_index).representation().Equals( - new_representation)); - DCHECK(old_descriptors->GetFieldType(modify_index)->NowIs(new_field_type)); - return old_map; + GeneralizeFieldType(field_owner, modify_index, new_representation, + new_field_type); + DCHECK(old_descriptors->GetDetails(modify_index) + .representation() + .Equals(new_representation)); + DCHECK( + old_descriptors->GetFieldType(modify_index)->NowIs(new_field_type)); + return old_map; + } } // Check the state of the root map. Handle root_map(old_map->FindRootMap(), isolate); if (!old_map->EquivalentToForTransition(*root_map)) { return CopyGeneralizeAllRepresentations(old_map, modify_index, store_mode, + new_kind, new_attributes, "GenAll_NotEquivalent"); } int root_nof = root_map->NumberOfOwnDescriptors(); - if (modify_index < root_nof) { + if (modify_index >= 0 && modify_index < root_nof) { PropertyDetails old_details = old_descriptors->GetDetails(modify_index); + if (old_details.kind() != new_kind || + old_details.attributes() != new_attributes) { + return CopyGeneralizeAllRepresentations(old_map, modify_index, store_mode, + new_kind, new_attributes, + "GenAll_RootModification1"); + } if ((old_details.type() != DATA && store_mode == FORCE_FIELD) || (old_details.type() == DATA && (!new_field_type->NowIs(old_descriptors->GetFieldType(modify_index)) || !new_representation.fits_into(old_details.representation())))) { return CopyGeneralizeAllRepresentations(old_map, modify_index, store_mode, - "GenAll_RootModification"); + new_kind, new_attributes, + "GenAll_RootModification2"); } } Handle target_map = root_map; for (int i = root_nof; i < old_nof; ++i) { PropertyDetails old_details = old_descriptors->GetDetails(i); - int j = target_map->SearchTransition(old_details.kind(), - old_descriptors->GetKey(i), - old_details.attributes()); + PropertyKind next_kind; + PropertyLocation next_location; + PropertyAttributes next_attributes; + Representation next_representation; + bool property_kind_reconfiguration = false; + + if (modify_index == i) { + DCHECK_EQ(FORCE_FIELD, store_mode); + property_kind_reconfiguration = old_details.kind() != new_kind; + + next_kind = new_kind; + next_location = kField; + next_attributes = new_attributes; + // If property kind is not reconfigured merge the result with + // representation/field type from the old descriptor. + next_representation = new_representation; + if (!property_kind_reconfiguration) { + next_representation = + next_representation.generalize(old_details.representation()); + } + + } else { + next_kind = old_details.kind(); + next_location = old_details.location(); + next_attributes = old_details.attributes(); + next_representation = old_details.representation(); + } + int j = target_map->SearchTransition(next_kind, old_descriptors->GetKey(i), + next_attributes); if (j == TransitionArray::kNotFound) break; Handle tmp_map(target_map->GetTransition(j), isolate); Handle tmp_descriptors = handle( @@ -2455,42 +2550,48 @@ Handle Map::GeneralizeRepresentation(Handle old_map, // Check if target map is incompatible. PropertyDetails tmp_details = tmp_descriptors->GetDetails(i); - PropertyType old_type = old_details.type(); - PropertyType tmp_type = tmp_details.type(); - DCHECK_EQ(old_details.attributes(), tmp_details.attributes()); - if ((tmp_type == ACCESSOR_CONSTANT || old_type == ACCESSOR_CONSTANT) && - (tmp_type != old_type || - tmp_descriptors->GetValue(i) != old_descriptors->GetValue(i))) { + DCHECK_EQ(next_kind, tmp_details.kind()); + DCHECK_EQ(next_attributes, tmp_details.attributes()); + if (next_kind == kAccessor && + !EqualImmutableValues(old_descriptors->GetValue(i), + tmp_descriptors->GetValue(i))) { return CopyGeneralizeAllRepresentations(old_map, modify_index, store_mode, + new_kind, new_attributes, "GenAll_Incompatible"); } - Representation old_representation = old_details.representation(); + if (next_location == kField && tmp_details.location() == kDescriptor) break; + Representation tmp_representation = tmp_details.representation(); - if (!old_representation.fits_into(tmp_representation) || - (!new_representation.fits_into(tmp_representation) && - modify_index == i)) { - break; - } - if (tmp_type == DATA) { - // Generalize the field type as necessary. - Handle old_field_type = - (old_type == DATA) ? handle(old_descriptors->GetFieldType(i), isolate) - : old_descriptors->GetValue(i) - ->OptimalType(isolate, tmp_representation); - if (modify_index == i) { - old_field_type = GeneralizeFieldType( - new_field_type, old_field_type, isolate); - } - GeneralizeFieldType(tmp_map, i, tmp_representation, old_field_type); - } else if (tmp_type == DATA_CONSTANT) { - if (old_type != DATA_CONSTANT || - old_descriptors->GetConstant(i) != tmp_descriptors->GetConstant(i)) { - break; + if (!next_representation.fits_into(tmp_representation)) break; + + PropertyLocation old_location = old_details.location(); + PropertyLocation tmp_location = tmp_details.location(); + if (tmp_location == kField) { + if (next_kind == kData) { + Handle next_field_type; + if (modify_index == i) { + next_field_type = new_field_type; + if (!property_kind_reconfiguration) { + Handle old_field_type = + GetFieldType(isolate, old_descriptors, i, + old_details.location(), tmp_representation); + next_field_type = + GeneralizeFieldType(next_field_type, old_field_type, isolate); + } + } else { + Handle old_field_type = + GetFieldType(isolate, old_descriptors, i, old_details.location(), + tmp_representation); + next_field_type = old_field_type; + } + GeneralizeFieldType(tmp_map, i, tmp_representation, next_field_type); } - } else { - DCHECK_EQ(tmp_type, old_type); - DCHECK_EQ(tmp_descriptors->GetValue(i), old_descriptors->GetValue(i)); + } else if (old_location == kField || + !EqualImmutableValues(old_descriptors->GetValue(i), + tmp_descriptors->GetValue(i))) { + break; } + DCHECK(!tmp_map->is_deprecated()); target_map = tmp_map; } @@ -2500,37 +2601,55 @@ Handle Map::GeneralizeRepresentation(Handle old_map, int target_nof = target_map->NumberOfOwnDescriptors(); if (target_nof == old_nof && (store_mode != FORCE_FIELD || - target_descriptors->GetDetails(modify_index).type() == DATA)) { - DCHECK(modify_index < target_nof); - DCHECK(new_representation.fits_into( - target_descriptors->GetDetails(modify_index).representation())); - DCHECK( - target_descriptors->GetDetails(modify_index).type() != DATA || - new_field_type->NowIs(target_descriptors->GetFieldType(modify_index))); + (modify_index >= 0 && + target_descriptors->GetDetails(modify_index).location() == kField))) { +#ifdef DEBUG + if (modify_index >= 0) { + PropertyDetails details = target_descriptors->GetDetails(modify_index); + DCHECK_EQ(new_kind, details.kind()); + DCHECK_EQ(new_attributes, details.attributes()); + DCHECK(new_representation.fits_into(details.representation())); + DCHECK(details.location() != kField || + new_field_type->NowIs( + target_descriptors->GetFieldType(modify_index))); + } +#endif return target_map; } // Find the last compatible target map in the transition tree. for (int i = target_nof; i < old_nof; ++i) { PropertyDetails old_details = old_descriptors->GetDetails(i); - int j = target_map->SearchTransition(old_details.kind(), - old_descriptors->GetKey(i), - old_details.attributes()); + PropertyKind next_kind; + PropertyAttributes next_attributes; + if (modify_index == i) { + next_kind = new_kind; + next_attributes = new_attributes; + } else { + next_kind = old_details.kind(); + next_attributes = old_details.attributes(); + } + int j = target_map->SearchTransition(next_kind, old_descriptors->GetKey(i), + next_attributes); if (j == TransitionArray::kNotFound) break; Handle tmp_map(target_map->GetTransition(j), isolate); Handle tmp_descriptors( tmp_map->instance_descriptors(), isolate); // Check if target map is compatible. +#ifdef DEBUG PropertyDetails tmp_details = tmp_descriptors->GetDetails(i); - DCHECK_EQ(old_details.attributes(), tmp_details.attributes()); - if ((tmp_details.type() == ACCESSOR_CONSTANT || - old_details.type() == ACCESSOR_CONSTANT) && - (tmp_details.type() != old_details.type() || - tmp_descriptors->GetValue(i) != old_descriptors->GetValue(i))) { + DCHECK_EQ(next_kind, tmp_details.kind()); + DCHECK_EQ(next_attributes, tmp_details.attributes()); +#endif + if (next_kind == kAccessor && + !EqualImmutableValues(old_descriptors->GetValue(i), + tmp_descriptors->GetValue(i))) { return CopyGeneralizeAllRepresentations(old_map, modify_index, store_mode, + new_kind, new_attributes, "GenAll_Incompatible"); } + DCHECK(!tmp_map->is_deprecated()); target_map = tmp_map; } target_nof = target_map->NumberOfOwnDescriptors(); @@ -2553,7 +2672,7 @@ Handle Map::GeneralizeRepresentation(Handle old_map, int current_offset = 0; for (int i = 0; i < root_nof; ++i) { PropertyDetails old_details = old_descriptors->GetDetails(i); - if (old_details.type() == DATA) { + if (old_details.location() == kField) { current_offset += old_details.field_width_in_words(); } Descriptor d(handle(old_descriptors->GetKey(i), isolate), @@ -2567,43 +2686,85 @@ Handle Map::GeneralizeRepresentation(Handle old_map, Handle target_key(target_descriptors->GetKey(i), isolate); PropertyDetails old_details = old_descriptors->GetDetails(i); PropertyDetails target_details = target_descriptors->GetDetails(i); - target_details = target_details.CopyWithRepresentation( - old_details.representation().generalize( - target_details.representation())); + + PropertyKind next_kind; + PropertyAttributes next_attributes; + PropertyLocation next_location; + Representation next_representation; + bool property_kind_reconfiguration = false; + if (modify_index == i) { - target_details = target_details.CopyWithRepresentation( - new_representation.generalize(target_details.representation())); - } - DCHECK_EQ(old_details.attributes(), target_details.attributes()); - if (old_details.type() == DATA || target_details.type() == DATA || - (modify_index == i && store_mode == FORCE_FIELD) || - (target_descriptors->GetValue(i) != old_descriptors->GetValue(i))) { - Handle old_field_type = - (old_details.type() == DATA) - ? handle(old_descriptors->GetFieldType(i), isolate) - : old_descriptors->GetValue(i) - ->OptimalType(isolate, target_details.representation()); - Handle target_field_type = - (target_details.type() == DATA) - ? handle(target_descriptors->GetFieldType(i), isolate) - : target_descriptors->GetValue(i) - ->OptimalType(isolate, target_details.representation()); - target_field_type = GeneralizeFieldType( - target_field_type, old_field_type, isolate); - if (modify_index == i) { - target_field_type = GeneralizeFieldType( - target_field_type, new_field_type, isolate); + DCHECK_EQ(FORCE_FIELD, store_mode); + property_kind_reconfiguration = old_details.kind() != new_kind; + + next_kind = new_kind; + next_attributes = new_attributes; + next_location = kField; + + // Merge new representation/field type with ones from the target + // descriptor. If property kind is not reconfigured merge the result with + // representation/field type from the old descriptor. + next_representation = + new_representation.generalize(target_details.representation()); + if (!property_kind_reconfiguration) { + next_representation = + next_representation.generalize(old_details.representation()); } - DataDescriptor d(target_key, current_offset, target_field_type, - target_details.attributes(), - target_details.representation()); - current_offset += d.GetDetails().field_width_in_words(); - new_descriptors->Set(i, &d); } else { - DCHECK_NE(DATA, target_details.type()); - Descriptor d(target_key, - handle(target_descriptors->GetValue(i), isolate), - target_details); + // Merge old_descriptor and target_descriptor entries. + DCHECK_EQ(target_details.kind(), old_details.kind()); + next_kind = target_details.kind(); + next_attributes = target_details.attributes(); + next_location = + old_details.location() == kField || + target_details.location() == kField || + !EqualImmutableValues(target_descriptors->GetValue(i), + old_descriptors->GetValue(i)) + ? kField + : kDescriptor; + + next_representation = old_details.representation().generalize( + target_details.representation()); + } + DCHECK_EQ(next_kind, target_details.kind()); + DCHECK_EQ(next_attributes, target_details.attributes()); + + if (next_location == kField) { + if (next_kind == kData) { + Handle target_field_type = + GetFieldType(isolate, target_descriptors, i, + target_details.location(), next_representation); + + Handle next_field_type; + if (modify_index == i) { + next_field_type = + GeneralizeFieldType(target_field_type, new_field_type, isolate); + if (!property_kind_reconfiguration) { + Handle old_field_type = + GetFieldType(isolate, old_descriptors, i, + old_details.location(), next_representation); + next_field_type = + GeneralizeFieldType(next_field_type, old_field_type, isolate); + } + } else { + Handle old_field_type = + GetFieldType(isolate, old_descriptors, i, old_details.location(), + next_representation); + next_field_type = + GeneralizeFieldType(target_field_type, old_field_type, isolate); + } + DataDescriptor d(target_key, current_offset, next_field_type, + next_attributes, next_representation); + current_offset += d.GetDetails().field_width_in_words(); + new_descriptors->Set(i, &d); + } else { + UNIMPLEMENTED(); // TODO(ishell): implement. + } + } else { + PropertyDetails details(next_attributes, next_kind, next_location, + next_representation); + Descriptor d(target_key, handle(target_descriptors->GetValue(i), isolate), + details); new_descriptors->Set(i, &d); } } @@ -2612,47 +2773,74 @@ Handle Map::GeneralizeRepresentation(Handle old_map, for (int i = target_nof; i < old_nof; ++i) { PropertyDetails old_details = old_descriptors->GetDetails(i); Handle old_key(old_descriptors->GetKey(i), isolate); + + // Merge old_descriptor entry and modified details together. + PropertyKind next_kind; + PropertyAttributes next_attributes; + PropertyLocation next_location; + Representation next_representation; + bool property_kind_reconfiguration = false; + if (modify_index == i) { - old_details = old_details.CopyWithRepresentation( - new_representation.generalize(old_details.representation())); - } - if (old_details.type() == DATA) { - Handle old_field_type( - old_descriptors->GetFieldType(i), isolate); - if (modify_index == i) { - old_field_type = GeneralizeFieldType( - old_field_type, new_field_type, isolate); + DCHECK_EQ(FORCE_FIELD, store_mode); + // In case of property kind reconfiguration it is not necessary to + // take into account representation/field type of the old descriptor. + property_kind_reconfiguration = old_details.kind() != new_kind; + + next_kind = new_kind; + next_attributes = new_attributes; + next_location = kField; + next_representation = new_representation; + if (!property_kind_reconfiguration) { + next_representation = + next_representation.generalize(old_details.representation()); } - DataDescriptor d(old_key, current_offset, old_field_type, - old_details.attributes(), old_details.representation()); - current_offset += d.GetDetails().field_width_in_words(); - new_descriptors->Set(i, &d); } else { - DCHECK(old_details.type() == DATA_CONSTANT || - old_details.type() == ACCESSOR_CONSTANT); - if (modify_index == i && store_mode == FORCE_FIELD) { - DataDescriptor d( - old_key, current_offset, - GeneralizeFieldType(old_descriptors->GetValue(i)->OptimalType( - isolate, old_details.representation()), - new_field_type, isolate), - old_details.attributes(), old_details.representation()); + next_kind = old_details.kind(); + next_attributes = old_details.attributes(); + next_location = old_details.location(); + next_representation = old_details.representation(); + } + + if (next_location == kField) { + if (next_kind == kData) { + Handle next_field_type; + if (modify_index == i) { + next_field_type = new_field_type; + if (!property_kind_reconfiguration) { + Handle old_field_type = + GetFieldType(isolate, old_descriptors, i, + old_details.location(), next_representation); + old_field_type = + GeneralizeFieldType(old_field_type, next_field_type, isolate); + } + } else { + Handle old_field_type = + GetFieldType(isolate, old_descriptors, i, old_details.location(), + next_representation); + next_field_type = old_field_type; + } + + DataDescriptor d(old_key, current_offset, next_field_type, + next_attributes, next_representation); current_offset += d.GetDetails().field_width_in_words(); new_descriptors->Set(i, &d); } else { - DCHECK_NE(DATA, old_details.type()); - Descriptor d(old_key, - handle(old_descriptors->GetValue(i), isolate), - old_details); - new_descriptors->Set(i, &d); + UNIMPLEMENTED(); // TODO(ishell): implement. } + } else { + PropertyDetails details(next_attributes, next_kind, next_location, + next_representation); + Descriptor d(old_key, handle(old_descriptors->GetValue(i), isolate), + details); + new_descriptors->Set(i, &d); } } new_descriptors->Sort(); DCHECK(store_mode != FORCE_FIELD || - new_descriptors->GetDetails(modify_index).type() == DATA); + new_descriptors->GetDetails(modify_index).location() == kField); Handle split_map(root_map->FindLastMatchMap( root_nof, old_nof, *new_descriptors), isolate); @@ -2661,21 +2849,31 @@ Handle Map::GeneralizeRepresentation(Handle old_map, Handle new_layout_descriptor = LayoutDescriptor::New(split_map, new_descriptors, old_nof); - PropertyDetails split_prop_details = old_descriptors->GetDetails(split_nof); + + PropertyKind split_kind; + PropertyAttributes split_attributes; + if (modify_index == split_nof) { + split_kind = new_kind; + split_attributes = new_attributes; + } else { + PropertyDetails split_prop_details = old_descriptors->GetDetails(split_nof); + split_kind = split_prop_details.kind(); + split_attributes = split_prop_details.attributes(); + } bool transition_target_deprecated = split_map->DeprecateTarget( - split_prop_details.kind(), old_descriptors->GetKey(split_nof), - split_prop_details.attributes(), *new_descriptors, - *new_layout_descriptor); + split_kind, old_descriptors->GetKey(split_nof), split_attributes, + *new_descriptors, *new_layout_descriptor); // If |transition_target_deprecated| is true then the transition array // already contains entry for given descriptor. This means that the transition // could be inserted regardless of whether transitions array is full or not. if (!transition_target_deprecated && !split_map->CanHaveMoreTransitions()) { return CopyGeneralizeAllRepresentations(old_map, modify_index, store_mode, + new_kind, new_attributes, "GenAll_CantHaveMoreTransitions"); } - if (FLAG_trace_generalization) { + if (FLAG_trace_generalization && modify_index >= 0) { PropertyDetails old_details = old_descriptors->GetDetails(modify_index); PropertyDetails new_details = new_descriptors->GetDetails(modify_index); Handle old_field_type = @@ -2692,7 +2890,7 @@ Handle Map::GeneralizeRepresentation(Handle old_map, isolate); old_map->PrintGeneralization( stdout, "", modify_index, split_nof, old_nof, - old_details.type() == DATA_CONSTANT && store_mode == FORCE_FIELD, + old_details.location() == kDescriptor && store_mode == FORCE_FIELD, old_details.representation(), new_details.representation(), *old_field_type, *new_field_type); } @@ -2713,10 +2911,11 @@ Handle Map::GeneralizeAllFieldRepresentations( Handle map) { Handle descriptors(map->instance_descriptors()); for (int i = 0; i < map->NumberOfOwnDescriptors(); ++i) { - if (descriptors->GetDetails(i).type() == DATA) { - map = GeneralizeRepresentation(map, i, Representation::Tagged(), - HeapType::Any(map->GetIsolate()), - FORCE_FIELD); + PropertyDetails details = descriptors->GetDetails(i); + if (details.type() == DATA) { + map = ReconfigureProperty(map, i, kData, details.attributes(), + Representation::Tagged(), + HeapType::Any(map->GetIsolate()), FORCE_FIELD); } } return map; @@ -2740,9 +2939,9 @@ MaybeHandle Map::TryUpdate(Handle map) { // static Handle Map::Update(Handle map) { if (!map->is_deprecated()) return map; - return GeneralizeRepresentation(map, 0, Representation::None(), - HeapType::None(map->GetIsolate()), - ALLOW_IN_DESCRIPTOR); + return ReconfigureProperty(map, -1, kData, NONE, Representation::None(), + HeapType::None(map->GetIsolate()), + ALLOW_IN_DESCRIPTOR); } @@ -3952,7 +4151,6 @@ MaybeHandle JSObject::SetOwnPropertyIgnoreAttributes( } it.ReconfigureDataProperty(value, attributes); - it.PrepareForDataProperty(value); value = it.WriteDataValue(value); if (is_observed) { @@ -3977,7 +4175,6 @@ MaybeHandle JSObject::SetOwnPropertyIgnoreAttributes( if (is_observed) old_value = it.GetDataValue(); it.ReconfigureDataProperty(value, attributes); - it.PrepareForDataProperty(value); value = it.WriteDataValue(value); if (is_observed) { @@ -5846,7 +6043,7 @@ int Map::NextFreePropertyIndex() { DescriptorArray* descs = instance_descriptors(); for (int i = 0; i < number_of_own_descriptors; i++) { PropertyDetails details = descs->GetDetails(i); - if (details.type() == DATA) { + if (details.location() == kField) { int candidate = details.field_index() + details.field_width_in_words(); if (candidate > free_index) free_index = candidate; } @@ -6841,7 +7038,7 @@ Handle Map::CopyInstallDescriptors( int unused_property_fields = map->unused_property_fields(); PropertyDetails details = descriptors->GetDetails(new_descriptor); - if (details.type() == DATA) { + if (details.location() == kField) { unused_property_fields = map->unused_property_fields() - 1; if (unused_property_fields < 0) { unused_property_fields += JSObject::kFieldsAdded; @@ -7059,11 +7256,13 @@ Handle Map::PrepareForDataProperty(Handle map, int descriptor, if (descriptors->CanHoldValue(descriptor, *value)) return map; Isolate* isolate = map->GetIsolate(); + PropertyAttributes attributes = + descriptors->GetDetails(descriptor).attributes(); Representation representation = value->OptimalRepresentation(); Handle type = value->OptimalType(isolate, representation); - return GeneralizeRepresentation(map, descriptor, representation, type, - FORCE_FIELD); + return ReconfigureProperty(map, descriptor, kData, attributes, representation, + type, FORCE_FIELD); } @@ -7120,15 +7319,29 @@ Handle Map::TransitionToDataProperty(Handle map, Handle name, } -Handle Map::ReconfigureDataProperty(Handle map, int descriptor, - PropertyAttributes attributes) { +Handle Map::ReconfigureExistingProperty(Handle map, int descriptor, + PropertyKind kind, + PropertyAttributes attributes) { // Dictionaries have to be reconfigured in-place. DCHECK(!map->is_dictionary_map()); - // For now, give up on transitioning and just create a unique map. - // TODO(verwaest/ishell): Cache transitions with different attributes. - return CopyGeneralizeAllRepresentations( - map, descriptor, FORCE_FIELD, attributes, "GenAll_AttributesMismatch"); + if (!map->GetBackPointer()->IsMap()) { + // There is no benefit from reconstructing transition tree for maps without + // back pointers. + return CopyGeneralizeAllRepresentations( + map, descriptor, FORCE_FIELD, kind, attributes, + "GenAll_AttributesMismatchProtoMap"); + } + + if (FLAG_trace_generalization) { + map->PrintReconfiguration(stdout, descriptor, kind, attributes); + } + + Isolate* isolate = map->GetIsolate(); + Handle new_map = ReconfigureProperty( + map, descriptor, kind, attributes, Representation::None(), + HeapType::None(isolate), FORCE_FIELD); + return new_map; } diff --git a/src/objects.h b/src/objects.h index 1e67f7f..d7d6e67 100644 --- a/src/objects.h +++ b/src/objects.h @@ -5889,23 +5889,15 @@ class Map: public HeapObject { static void GeneralizeFieldType(Handle map, int modify_index, Representation new_representation, Handle new_field_type); - static Handle GeneralizeRepresentation( - Handle map, - int modify_index, - Representation new_representation, - Handle new_field_type, - StoreMode store_mode); + static Handle ReconfigureProperty(Handle map, int modify_index, + PropertyKind new_kind, + PropertyAttributes new_attributes, + Representation new_representation, + Handle new_field_type, + StoreMode store_mode); static Handle CopyGeneralizeAllRepresentations( - Handle map, - int modify_index, - StoreMode store_mode, - PropertyAttributes attributes, - const char* reason); - static Handle CopyGeneralizeAllRepresentations( - Handle map, - int modify_index, - StoreMode store_mode, - const char* reason); + Handle map, int modify_index, StoreMode store_mode, + PropertyKind kind, PropertyAttributes attributes, const char* reason); static Handle PrepareForDataProperty(Handle old_map, int descriptor_number, @@ -6128,8 +6120,10 @@ class Map: public HeapObject { static Handle TransitionToAccessorProperty( Handle map, Handle name, AccessorComponent component, Handle accessor, PropertyAttributes attributes); - static Handle ReconfigureDataProperty(Handle map, int descriptor, - PropertyAttributes attributes); + static Handle ReconfigureExistingProperty(Handle map, + int descriptor, + PropertyKind kind, + PropertyAttributes attributes); inline void AppendDescriptor(Descriptor* desc); @@ -6414,6 +6408,9 @@ class Map: public HeapObject { Descriptor* descriptor, int index, TransitionFlag flag); + static MUST_USE_RESULT MaybeHandle TryReconfigureExistingProperty( + Handle map, int descriptor, PropertyKind kind, + PropertyAttributes attributes, const char** reason); static Handle CopyNormalized(Handle map, PropertyNormalizationMode mode); @@ -6447,6 +6444,8 @@ class Map: public HeapObject { Representation new_representation, Handle new_type); + void PrintReconfiguration(FILE* file, int modify_index, PropertyKind kind, + PropertyAttributes attributes); void PrintGeneralization(FILE* file, const char* reason, int modify_index, @@ -10506,6 +10505,14 @@ class AccessorPair: public Struct { if (!setter->IsNull()) set_setter(setter); } + bool Equals(AccessorPair* pair) { + return (this == pair) || pair->Equals(getter(), setter()); + } + + bool Equals(Object* getter_value, Object* setter_value) { + return (getter() == getter_value) && (setter() == setter_value); + } + bool ContainsAccessor() { return IsJSAccessor(getter()) || IsJSAccessor(setter()); } diff --git a/src/property-details.h b/src/property-details.h index 6a84c44..135a079 100644 --- a/src/property-details.h +++ b/src/property-details.h @@ -212,6 +212,15 @@ class PropertyDetails BASE_EMBEDDED { | FieldIndexField::encode(field_index); } + PropertyDetails(PropertyAttributes attributes, PropertyKind kind, + PropertyLocation location, Representation representation, + int field_index = 0) { + value_ = KindField::encode(kind) | LocationField::encode(location) | + AttributesField::encode(attributes) | + RepresentationField::encode(EncodeRepresentation(representation)) | + FieldIndexField::encode(field_index); + } + int pointer() const { return DescriptorPointer::decode(value_); } PropertyDetails set_pointer(int i) { return PropertyDetails(value_, i); } diff --git a/src/property.cc b/src/property.cc index dbe0afc..63837d3 100644 --- a/src/property.cc +++ b/src/property.cc @@ -69,9 +69,9 @@ std::ostream& operator<<(std::ostream& os, os << "immutable "; } os << (details.kind() == kData ? "data" : "accessor"); + os << ": " << details.representation().Mnemonic(); if (details.location() == kField) { - os << ": " << details.representation().Mnemonic() - << ", field_index: " << details.field_index(); + os << ", field_index: " << details.field_index(); } return os << ", p: " << details.pointer() << ", attrs: " << details.attributes() << ")"; diff --git a/test/cctest/cctest.gyp b/test/cctest/cctest.gyp index ffc11fa..1b851e4 100644 --- a/test/cctest/cctest.gyp +++ b/test/cctest/cctest.gyp @@ -137,6 +137,7 @@ 'test-microtask-delivery.cc', 'test-mark-compact.cc', 'test-mementos.cc', + 'test-migrations.cc', 'test-object-observe.cc', 'test-ordered-hash-table.cc', 'test-parsing.cc', diff --git a/test/cctest/test-migrations.cc b/test/cctest/test-migrations.cc new file mode 100644 index 0000000..79e23c2 --- /dev/null +++ b/test/cctest/test-migrations.cc @@ -0,0 +1,2017 @@ +// Copyright 2015 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. + +#include +#include + +#include "src/v8.h" + +#include "src/code-stubs.h" +#include "src/compilation-cache.h" +#include "src/execution.h" +#include "src/factory.h" +#include "src/global-handles.h" +#include "src/ic/stub-cache.h" +#include "src/macro-assembler.h" +#include "src/smart-pointers.h" +#include "test/cctest/cctest.h" + +using namespace v8::internal; + + +// TODO(ishell): fix this once ReconfigureProperty supports "non equivalent" +// transitions. +const bool IS_NON_EQUIVALENT_TRANSITION_SUPPORTED = false; + + +// TODO(ishell): fix this once TransitionToPrototype stops generalizing +// all field representations (similar to crbug/448711 where elements kind +// and observed transitions caused generalization of all field representations). +const bool IS_PROTO_TRANS_ISSUE_FIXED = false; + + +// TODO(ishell): fix this once TransitionToAccessorProperty is able to always +// keep map in fast mode. +const bool IS_ACCESSOR_FIELD_SUPPORTED = false; + + +// Number of properties used in the tests. +const int kPropCount = 7; + + +// +// Helper functions. +// + +static Handle MakeString(const char* str) { + Isolate* isolate = CcTest::i_isolate(); + Factory* factory = isolate->factory(); + return factory->InternalizeUtf8String(str); +} + + +static Handle MakeName(const char* str, int suffix) { + EmbeddedVector buffer; + SNPrintF(buffer, "%s%d", str, suffix); + return MakeString(buffer.start()); +} + + +static Handle CreateAccessorPair(bool with_getter, + bool with_setter) { + Isolate* isolate = CcTest::i_isolate(); + Factory* factory = isolate->factory(); + Handle pair = factory->NewAccessorPair(); + if (with_getter) { + pair->set_getter(*factory->NewFunction(factory->empty_string())); + } + if (with_setter) { + pair->set_setter(*factory->NewFunction(factory->empty_string())); + } + return pair; +} + + +static bool EqualDetails(DescriptorArray* descriptors, int descriptor, + PropertyType type, PropertyAttributes attributes, + Representation representation, int field_index = -1) { + PropertyDetails details = descriptors->GetDetails(descriptor); + if (details.type() != type) return false; + if (details.attributes() != attributes) return false; + if (!details.representation().Equals(representation)) return false; + if (field_index >= 0 && details.field_index() != field_index) return false; + return true; +} + + +class Expectations { + static const int MAX_PROPERTIES = 10; + Isolate* isolate_; + PropertyType types_[MAX_PROPERTIES]; + PropertyAttributes attributes_[MAX_PROPERTIES]; + Representation representations_[MAX_PROPERTIES]; + // HeapType for kField, value for DATA_CONSTANT and getter for + // ACCESSOR_CONSTANT. + Handle values_[MAX_PROPERTIES]; + // Setter for ACCESSOR_CONSTANT. + Handle setter_values_[MAX_PROPERTIES]; + int number_of_properties_; + + public: + explicit Expectations(Isolate* isolate) + : isolate_(isolate), number_of_properties_(0) {} + + void Init(int index, PropertyType type, PropertyAttributes attributes, + Representation representation, Handle value) { + DCHECK(index < MAX_PROPERTIES); + types_[index] = type; + attributes_[index] = attributes; + representations_[index] = representation; + values_[index] = value; + } + + void Print() const { + OFStream os(stdout); + os << "Expectations: #" << number_of_properties_ << "\n"; + for (int i = 0; i < number_of_properties_; i++) { + os << " " << i << ": "; + os << "Descriptor @ "; + if (types_[i] == ACCESSOR_CONSTANT) { + os << "(get: " << Brief(*values_[i]) + << ", set: " << Brief(*setter_values_[i]) << ") "; + } else { + os << Brief(*values_[i]); + } + os << " ("; + switch (types_[i]) { + case DATA_CONSTANT: + os << "immutable "; + // Fall through. + case DATA: + os << "data"; + break; + + case ACCESSOR_CONSTANT: + os << "immutable "; + // Fall through. + case ACCESSOR: + os << "accessor"; + break; + } + os << ": " << representations_[i].Mnemonic(); + os << ", attrs: " << attributes_[i] << ")\n"; + } + } + + Handle GetFieldType(int index) { + CHECK(index < MAX_PROPERTIES); + CHECK(types_[index] == DATA || types_[index] == ACCESSOR); + return Handle::cast(values_[index]); + } + + void SetDataField(int index, PropertyAttributes attrs, + Representation representation, Handle value) { + Init(index, DATA, attrs, representation, value); + } + + void SetDataField(int index, Representation representation, + Handle value) { + SetDataField(index, attributes_[index], representation, value); + } + + void SetAccessorField(int index, PropertyAttributes attrs) { + Init(index, ACCESSOR, attrs, Representation::Tagged(), + HeapType::Any(isolate_)); + } + + void SetAccessorField(int index) { + SetAccessorField(index, attributes_[index]); + } + + void SetDataConstant(int index, PropertyAttributes attrs, + Handle value) { + Init(index, DATA_CONSTANT, attrs, Representation::HeapObject(), value); + } + + void SetDataConstant(int index, Handle value) { + SetDataConstant(index, attributes_[index], value); + } + + void SetAccessorConstant(int index, PropertyAttributes attrs, + Handle getter, Handle setter) { + Init(index, ACCESSOR_CONSTANT, attrs, Representation::Tagged(), getter); + setter_values_[index] = setter; + } + + void SetAccessorConstantComponent(int index, PropertyAttributes attrs, + AccessorComponent component, + Handle accessor) { + CHECK_EQ(ACCESSOR_CONSTANT, types_[index]); + CHECK(index < number_of_properties_); + if (component == ACCESSOR_GETTER) { + values_[index] = accessor; + } else { + setter_values_[index] = accessor; + } + } + + void SetAccessorConstant(int index, PropertyAttributes attrs, + Handle pair) { + Handle getter = handle(pair->getter(), isolate_); + Handle setter = handle(pair->setter(), isolate_); + SetAccessorConstant(index, attrs, getter, setter); + } + + void SetAccessorConstant(int index, Handle getter, + Handle setter) { + SetAccessorConstant(index, attributes_[index], getter, setter); + } + + void SetAccessorConstant(int index, Handle pair) { + Handle getter = handle(pair->getter(), isolate_); + Handle setter = handle(pair->setter(), isolate_); + SetAccessorConstant(index, getter, setter); + } + + void GeneralizeRepresentation(int index) { + CHECK(index < number_of_properties_); + representations_[index] = Representation::Tagged(); + if (types_[index] == DATA || types_[index] == ACCESSOR) { + values_[index] = HeapType::Any(isolate_); + } + } + + + bool Check(DescriptorArray* descriptors, int descriptor) const { + PropertyType type = types_[descriptor]; + if (!EqualDetails(descriptors, descriptor, type, attributes_[descriptor], + representations_[descriptor])) { + return false; + } + Object* expected_value = *values_[descriptor]; + Object* value = descriptors->GetValue(descriptor); + switch (type) { + case DATA: + case ACCESSOR: + return HeapType::cast(expected_value)->Equals(HeapType::cast(value)); + + case DATA_CONSTANT: + return value == expected_value; + + case ACCESSOR_CONSTANT: { + if (value == expected_value) return true; + if (!value->IsAccessorPair()) return false; + AccessorPair* pair = AccessorPair::cast(value); + return pair->Equals(expected_value, *setter_values_[descriptor]); + } + } + UNREACHABLE(); + return false; + } + + bool Check(Map* map, int expected_nof) const { + CHECK(number_of_properties_ <= MAX_PROPERTIES); + CHECK_EQ(expected_nof, map->NumberOfOwnDescriptors()); + CHECK(!map->is_dictionary_map()); + + DescriptorArray* descriptors = map->instance_descriptors(); + CHECK(expected_nof <= number_of_properties_); + for (int i = 0; i < expected_nof; i++) { + if (!Check(descriptors, i)) { + Print(); + Check(descriptors, i); + return false; + } + } + return true; + } + + bool Check(Map* map) const { return Check(map, number_of_properties_); } + + + // + // Helper methods for initializing expectations and adding properties to + // given |map|. + // + + Handle AddDataField(Handle map, PropertyAttributes attributes, + Representation representation, + Handle heap_type) { + CHECK_EQ(number_of_properties_, map->NumberOfOwnDescriptors()); + int property_index = number_of_properties_++; + SetDataField(property_index, attributes, representation, heap_type); + + Handle name = MakeName("prop", property_index); + return Map::CopyWithField(map, name, heap_type, attributes, representation, + INSERT_TRANSITION).ToHandleChecked(); + } + + Handle AddDataConstant(Handle map, PropertyAttributes attributes, + Handle value) { + CHECK_EQ(number_of_properties_, map->NumberOfOwnDescriptors()); + int property_index = number_of_properties_++; + SetDataConstant(property_index, attributes, value); + + Handle name = MakeName("prop", property_index); + return Map::CopyWithConstant(map, name, value, attributes, + INSERT_TRANSITION).ToHandleChecked(); + } + + Handle TransitionToDataField(Handle map, + PropertyAttributes attributes, + Representation representation, + Handle heap_type, + Handle value) { + CHECK_EQ(number_of_properties_, map->NumberOfOwnDescriptors()); + int property_index = number_of_properties_++; + SetDataField(property_index, attributes, representation, heap_type); + + Handle name = MakeName("prop", property_index); + return Map::TransitionToDataProperty( + map, name, value, attributes, Object::CERTAINLY_NOT_STORE_FROM_KEYED); + } + + Handle TransitionToDataConstant(Handle map, + PropertyAttributes attributes, + Handle value) { + CHECK_EQ(number_of_properties_, map->NumberOfOwnDescriptors()); + int property_index = number_of_properties_++; + SetDataConstant(property_index, attributes, value); + + Handle name = MakeName("prop", property_index); + return Map::TransitionToDataProperty( + map, name, value, attributes, Object::CERTAINLY_NOT_STORE_FROM_KEYED); + } + + Handle FollowDataTransition(Handle map, + PropertyAttributes attributes, + Representation representation, + Handle heap_type) { + CHECK_EQ(number_of_properties_, map->NumberOfOwnDescriptors()); + int property_index = number_of_properties_++; + SetDataField(property_index, attributes, representation, heap_type); + + Handle name = MakeName("prop", property_index); + int t = map->SearchTransition(kData, *name, attributes); + CHECK_NE(TransitionArray::kNotFound, t); + return handle(map->GetTransition(t)); + } + + Handle AddAccessorConstant(Handle map, + PropertyAttributes attributes, + Handle pair) { + CHECK_EQ(number_of_properties_, map->NumberOfOwnDescriptors()); + int property_index = number_of_properties_++; + SetAccessorConstant(property_index, attributes, pair); + + Handle name = MakeName("prop", property_index); + + AccessorConstantDescriptor new_desc(name, pair, attributes); + return Map::CopyInsertDescriptor(map, &new_desc, INSERT_TRANSITION); + } + + Handle AddAccessorConstant(Handle map, + PropertyAttributes attributes, + Handle getter, + Handle setter) { + CHECK_EQ(number_of_properties_, map->NumberOfOwnDescriptors()); + int property_index = number_of_properties_++; + SetAccessorConstant(property_index, attributes, getter, setter); + + Handle name = MakeName("prop", property_index); + + CHECK(!getter->IsNull() || !setter->IsNull()); + Factory* factory = isolate_->factory(); + + if (!getter->IsNull()) { + Handle pair = factory->NewAccessorPair(); + pair->SetComponents(*getter, *factory->null_value()); + AccessorConstantDescriptor new_desc(name, pair, attributes); + map = Map::CopyInsertDescriptor(map, &new_desc, INSERT_TRANSITION); + } + if (!setter->IsNull()) { + Handle pair = factory->NewAccessorPair(); + pair->SetComponents(*getter, *setter); + AccessorConstantDescriptor new_desc(name, pair, attributes); + map = Map::CopyInsertDescriptor(map, &new_desc, INSERT_TRANSITION); + } + return map; + } + + Handle TransitionToAccessorConstant(Handle map, + PropertyAttributes attributes, + Handle pair) { + CHECK_EQ(number_of_properties_, map->NumberOfOwnDescriptors()); + int property_index = number_of_properties_++; + SetAccessorConstant(property_index, attributes, pair); + + Handle name = MakeName("prop", property_index); + + Isolate* isolate = CcTest::i_isolate(); + Handle getter(pair->getter(), isolate); + Handle setter(pair->setter(), isolate); + + map = Map::TransitionToAccessorProperty(map, name, ACCESSOR_GETTER, getter, + attributes); + CHECK(!map->is_deprecated()); + CHECK(!map->is_dictionary_map()); + + map = Map::TransitionToAccessorProperty(map, name, ACCESSOR_SETTER, setter, + attributes); + CHECK(!map->is_deprecated()); + CHECK(!map->is_dictionary_map()); + return map; + } +}; + + +//////////////////////////////////////////////////////////////////////////////// +// A set of tests for property reconfiguration that makes new transition tree +// branch. +// + +TEST(ReconfigureAccessorToNonExistingDataField) { + CcTest::InitializeVM(); + v8::HandleScope scope(CcTest::isolate()); + Isolate* isolate = CcTest::i_isolate(); + Handle any_type = HeapType::Any(isolate); + Handle none_type = HeapType::None(isolate); + Handle pair = CreateAccessorPair(true, true); + + Expectations expectations(isolate); + + // Create a map, add required properties to it and initialize expectations. + Handle initial_map = Map::Create(isolate, 0); + Handle map = initial_map; + map = expectations.AddAccessorConstant(map, NONE, pair); + + CHECK(!map->is_deprecated()); + CHECK(map->is_stable()); + CHECK(expectations.Check(*map)); + + Handle new_map = Map::ReconfigureProperty( + map, 0, kData, NONE, Representation::None(), none_type, FORCE_FIELD); + // |map| did not change. + CHECK(!map->is_deprecated()); + CHECK(map->is_stable()); + CHECK(expectations.Check(*map)); + + expectations.SetDataField(0, NONE, Representation::None(), none_type); + + CHECK(!new_map->is_deprecated()); + CHECK(new_map->is_stable()); + CHECK(expectations.Check(*new_map)); + + CHECK_EQ(*new_map, *Map::ReconfigureProperty(map, 0, kData, NONE, + Representation::None(), + none_type, FORCE_FIELD)); + + Handle value(Smi::FromInt(0), isolate); + Handle prepared_map = Map::PrepareForDataProperty(new_map, 0, value); + // None to Smi generalization is trivial, map does not change. + CHECK_EQ(*new_map, *prepared_map); + + expectations.SetDataField(0, NONE, Representation::Smi(), any_type); + CHECK(prepared_map->is_stable()); + CHECK(expectations.Check(*prepared_map)); + + // Now create an object with |map|, migrate it to |prepared_map| and ensure + // that the data property is uninitialized. + Factory* factory = isolate->factory(); + Handle obj = factory->NewJSObjectFromMap(map); + JSObject::MigrateToMap(obj, prepared_map); + FieldIndex index = FieldIndex::ForDescriptor(*prepared_map, 0); + CHECK(obj->RawFastPropertyAt(index)->IsUninitialized()); +#ifdef VERIFY_HEAP + obj->ObjectVerify(); +#endif +} + + +// This test checks that the LookupIterator machinery involved in +// JSObject::SetOwnPropertyIgnoreAttributes() does not try to migrate object +// to a map with a property with None representation. +TEST(ReconfigureAccessorToNonExistingDataFieldHeavy) { + CcTest::InitializeVM(); + Isolate* isolate = CcTest::i_isolate(); + Factory* factory = isolate->factory(); + v8::HandleScope scope(CcTest::isolate()); + + CompileRun( + "function getter() { return 1; };" + "function setter() {};" + "var o = {};" + "Object.defineProperty(o, 'foo', " + " { get: getter, set: setter, " + " configurable: true, enumerable: true});"); + + Handle foo_str = factory->InternalizeUtf8String("foo"); + Handle obj_name = factory->InternalizeUtf8String("o"); + + Handle obj_value = + Object::GetProperty(isolate->global_object(), obj_name).ToHandleChecked(); + CHECK(obj_value->IsJSObject()); + Handle obj = Handle::cast(obj_value); + + CHECK_EQ(1, obj->map()->NumberOfOwnDescriptors()); + CHECK(obj->map()->instance_descriptors()->GetValue(0)->IsAccessorPair()); + + Handle value(Smi::FromInt(42), isolate); + JSObject::SetOwnPropertyIgnoreAttributes( + obj, foo_str, value, NONE, JSObject::DONT_FORCE_FIELD).ToHandleChecked(); + + // Check that the property contains |value|. + CHECK_EQ(1, obj->map()->NumberOfOwnDescriptors()); + FieldIndex index = FieldIndex::ForDescriptor(obj->map(), 0); + Object* the_value = obj->RawFastPropertyAt(index); + CHECK(the_value->IsSmi()); + CHECK_EQ(42, Smi::cast(the_value)->value()); +} + + +//////////////////////////////////////////////////////////////////////////////// +// A set of tests for representation generalization case. +// + +static void TestGeneralizeRepresentation(Representation from_representation, + Handle from_type, + Representation to_representation, + Handle to_type, + Representation expected_representation, + Handle expected_type) { + Isolate* isolate = CcTest::i_isolate(); + + Expectations expectations(isolate); + + // Create a map, add required properties to it and initialize expectations. + Handle initial_map = Map::Create(isolate, 0); + Handle map = initial_map; + for (int i = 0; i < kPropCount; i++) { + map = expectations.AddDataField(map, NONE, from_representation, from_type); + } + CHECK(!map->is_deprecated()); + CHECK(map->is_stable()); + CHECK(expectations.Check(*map)); + + Zone zone; + FakeStubForTesting stub(isolate); + + // Create new maps by generalizing representation of propX field. + Handle maps[kPropCount]; + for (int i = 0; i < kPropCount; i++) { + Handle field_owner(map->FindFieldOwner(i), isolate); + CompilationInfo info(&stub, isolate, &zone); + CHECK(!info.HasAbortedDueToDependencyChange()); + + Map::AddDependentCompilationInfo(field_owner, + DependentCode::kFieldTypeGroup, &info); + + Handle new_map = Map::ReconfigureProperty( + map, i, kData, NONE, to_representation, to_type, FORCE_FIELD); + maps[i] = new_map; + + expectations.SetDataField(i, expected_representation, expected_type); + + CHECK(map->is_deprecated()); + CHECK(!info.HasAbortedDueToDependencyChange()); + info.RollbackDependencies(); // Properly cleanup compilation info. + + CHECK_NE(*map, *new_map); + CHECK(i == 0 || maps[i - 1]->is_deprecated()); + + CHECK(!new_map->is_deprecated()); + CHECK(!new_map->is_dictionary_map()); + CHECK(expectations.Check(*new_map)); + } + + Handle active_map = maps[kPropCount - 1]; + CHECK(!active_map->is_deprecated()); + + // Update all deprecated maps and check that they are now the same. + CHECK_EQ(*active_map, *Map::Update(map)); + for (int i = 0; i < kPropCount; i++) { + CHECK_EQ(*active_map, *Map::Update(maps[i])); + } +} + + +static void TestGeneralizeRepresentationTrivial( + Representation from_representation, Handle from_type, + Representation to_representation, Handle to_type, + Representation expected_representation, Handle expected_type, + bool expected_field_type_dependency = true) { + Isolate* isolate = CcTest::i_isolate(); + + Expectations expectations(isolate); + + // Create a map, add required properties to it and initialize expectations. + Handle initial_map = Map::Create(isolate, 0); + Handle map = initial_map; + for (int i = 0; i < kPropCount; i++) { + map = expectations.AddDataField(map, NONE, from_representation, from_type); + } + CHECK(!map->is_deprecated()); + CHECK(map->is_stable()); + CHECK(expectations.Check(*map)); + + Zone zone; + FakeStubForTesting stub(isolate); + + // Create new maps by generalizing representation of propX field. + for (int i = 0; i < kPropCount; i++) { + Handle field_owner(map->FindFieldOwner(i), isolate); + CompilationInfo info(&stub, isolate, &zone); + CHECK(!info.HasAbortedDueToDependencyChange()); + + Map::AddDependentCompilationInfo(field_owner, + DependentCode::kFieldTypeGroup, &info); + + Handle new_map = Map::ReconfigureProperty( + map, i, kData, NONE, to_representation, to_type, FORCE_FIELD); + + expectations.SetDataField(i, expected_representation, expected_type); + + CHECK_EQ(*map, *new_map); + CHECK_EQ(expected_field_type_dependency, + info.HasAbortedDueToDependencyChange()); + + info.RollbackDependencies(); // Properly cleanup compilation info. + + CHECK_EQ(*map, *new_map); + CHECK(!new_map->is_deprecated()); + CHECK(!new_map->is_dictionary_map()); + CHECK(expectations.Check(*new_map)); + } + + CHECK_EQ(*map, *Map::Update(map)); +} + + +TEST(GeneralizeRepresentationSmiToDouble) { + CcTest::InitializeVM(); + v8::HandleScope scope(CcTest::isolate()); + Isolate* isolate = CcTest::i_isolate(); + Handle any_type = HeapType::Any(isolate); + + TestGeneralizeRepresentation(Representation::Smi(), any_type, + Representation::Double(), any_type, + Representation::Double(), any_type); +} + + +TEST(GeneralizeRepresentationSmiToTagged) { + CcTest::InitializeVM(); + v8::HandleScope scope(CcTest::isolate()); + Isolate* isolate = CcTest::i_isolate(); + Handle any_type = HeapType::Any(isolate); + Handle value_type = + HeapType::Class(Map::Create(isolate, 0), isolate); + + TestGeneralizeRepresentation(Representation::Smi(), any_type, + Representation::HeapObject(), value_type, + Representation::Tagged(), any_type); +} + + +TEST(GeneralizeRepresentationDoubleToTagged) { + CcTest::InitializeVM(); + v8::HandleScope scope(CcTest::isolate()); + Isolate* isolate = CcTest::i_isolate(); + Handle any_type = HeapType::Any(isolate); + Handle value_type = + HeapType::Class(Map::Create(isolate, 0), isolate); + + TestGeneralizeRepresentation(Representation::Double(), any_type, + Representation::HeapObject(), value_type, + Representation::Tagged(), any_type); +} + + +TEST(GeneralizeRepresentationHeapObjectToTagged) { + CcTest::InitializeVM(); + v8::HandleScope scope(CcTest::isolate()); + Isolate* isolate = CcTest::i_isolate(); + Handle any_type = HeapType::Any(isolate); + Handle value_type = + HeapType::Class(Map::Create(isolate, 0), isolate); + + TestGeneralizeRepresentation(Representation::HeapObject(), value_type, + Representation::Smi(), any_type, + Representation::Tagged(), any_type); +} + + +TEST(GeneralizeRepresentationHeapObjectToHeapObject) { + CcTest::InitializeVM(); + v8::HandleScope scope(CcTest::isolate()); + Isolate* isolate = CcTest::i_isolate(); + Handle any_type = HeapType::Any(isolate); + + const int kMaxClassesPerFieldType = 5; + Handle current_type = + HeapType::Class(Map::Create(isolate, 0), isolate); + + for (int i = 0; i < kMaxClassesPerFieldType; i++) { + Handle new_type = + HeapType::Class(Map::Create(isolate, 0), isolate); + + Handle expected_type = + (i < kMaxClassesPerFieldType - 1) + ? HeapType::Union(current_type, new_type, isolate) + : any_type; + + TestGeneralizeRepresentationTrivial( + Representation::HeapObject(), current_type, + Representation::HeapObject(), new_type, Representation::HeapObject(), + expected_type); + current_type = expected_type; + } + + Handle new_type = HeapType::Class(Map::Create(isolate, 0), isolate); + + TestGeneralizeRepresentationTrivial( + Representation::HeapObject(), any_type, Representation::HeapObject(), + new_type, Representation::HeapObject(), any_type, false); +} + + +TEST(GeneralizeRepresentationNoneToSmi) { + CcTest::InitializeVM(); + v8::HandleScope scope(CcTest::isolate()); + Isolate* isolate = CcTest::i_isolate(); + Handle none_type = HeapType::None(isolate); + Handle any_type = HeapType::Any(isolate); + + // None -> Smi representation change is trivial. + TestGeneralizeRepresentationTrivial(Representation::None(), none_type, + Representation::Smi(), any_type, + Representation::Smi(), any_type); +} + + +TEST(GeneralizeRepresentationNoneToDouble) { + CcTest::InitializeVM(); + v8::HandleScope scope(CcTest::isolate()); + Isolate* isolate = CcTest::i_isolate(); + Handle none_type = HeapType::None(isolate); + Handle any_type = HeapType::Any(isolate); + + // None -> Double representation change is NOT trivial. + TestGeneralizeRepresentation(Representation::None(), none_type, + Representation::Double(), any_type, + Representation::Double(), any_type); +} + + +TEST(GeneralizeRepresentationNoneToHeapObject) { + CcTest::InitializeVM(); + v8::HandleScope scope(CcTest::isolate()); + Isolate* isolate = CcTest::i_isolate(); + Handle none_type = HeapType::None(isolate); + Handle value_type = + HeapType::Class(Map::Create(isolate, 0), isolate); + + // None -> HeapObject representation change is trivial. + TestGeneralizeRepresentationTrivial(Representation::None(), none_type, + Representation::HeapObject(), value_type, + Representation::HeapObject(), value_type); +} + + +TEST(GeneralizeRepresentationNoneToTagged) { + CcTest::InitializeVM(); + v8::HandleScope scope(CcTest::isolate()); + Isolate* isolate = CcTest::i_isolate(); + Handle none_type = HeapType::None(isolate); + Handle any_type = HeapType::Any(isolate); + + // None -> HeapObject representation change is trivial. + TestGeneralizeRepresentationTrivial(Representation::None(), none_type, + Representation::Tagged(), any_type, + Representation::Tagged(), any_type); +} + + +//////////////////////////////////////////////////////////////////////////////// +// A set of tests for representation generalization case with kAccessor +// properties. +// + +TEST(GeneralizeRepresentationWithAccessorProperties) { + CcTest::InitializeVM(); + v8::HandleScope scope(CcTest::isolate()); + Isolate* isolate = CcTest::i_isolate(); + Handle any_type = HeapType::Any(isolate); + Handle pair = CreateAccessorPair(true, true); + + const int kAccessorProp = kPropCount / 2; + Expectations expectations(isolate); + + // Create a map, add required properties to it and initialize expectations. + Handle initial_map = Map::Create(isolate, 0); + Handle map = initial_map; + for (int i = 0; i < kPropCount; i++) { + if (i == kAccessorProp) { + map = expectations.AddAccessorConstant(map, NONE, pair); + } else { + map = + expectations.AddDataField(map, NONE, Representation::Smi(), any_type); + } + } + CHECK(!map->is_deprecated()); + CHECK(map->is_stable()); + CHECK(expectations.Check(*map)); + + // Create new maps by generalizing representation of propX field. + Handle maps[kPropCount]; + for (int i = 0; i < kPropCount; i++) { + if (i == kAccessorProp) { + // Skip accessor property reconfiguration. + maps[i] = maps[i - 1]; + continue; + } + Handle new_map = Map::ReconfigureProperty( + map, i, kData, NONE, Representation::Double(), any_type, FORCE_FIELD); + maps[i] = new_map; + + expectations.SetDataField(i, Representation::Double(), any_type); + + CHECK(map->is_deprecated()); + CHECK_NE(*map, *new_map); + CHECK(i == 0 || maps[i - 1]->is_deprecated()); + + CHECK(!new_map->is_deprecated()); + CHECK(!new_map->is_dictionary_map()); + CHECK(expectations.Check(*new_map)); + } + + Handle active_map = maps[kPropCount - 1]; + CHECK(!active_map->is_deprecated()); + + // Update all deprecated maps and check that they are now the same. + CHECK_EQ(*active_map, *Map::Update(map)); + for (int i = 0; i < kPropCount; i++) { + CHECK_EQ(*active_map, *Map::Update(maps[i])); + } +} + + +//////////////////////////////////////////////////////////////////////////////// +// A set of tests for attribute reconfiguration case. +// + +// This test ensures that representation/field type generalization is correctly +// propagated from one branch of transition tree (|map2|) to another (|map|). +// +// - p2B - p3 - p4: |map2| +// / +// {} - p0 - p1 - p2A - p3 - p4: |map| +// +// where "p2A" and "p2B" differ only in the attributes. +// +static void TestReconfigureDataFieldAttribute_GeneralizeRepresentation( + Representation from_representation, Handle from_type, + Representation to_representation, Handle to_type, + Representation expected_representation, Handle expected_type) { + Isolate* isolate = CcTest::i_isolate(); + + Expectations expectations(isolate); + + // Create a map, add required properties to it and initialize expectations. + Handle initial_map = Map::Create(isolate, 0); + Handle map = initial_map; + for (int i = 0; i < kPropCount; i++) { + map = expectations.AddDataField(map, NONE, from_representation, from_type); + } + CHECK(!map->is_deprecated()); + CHECK(map->is_stable()); + CHECK(expectations.Check(*map)); + + + // Create another branch in transition tree (property at index |kSplitProp| + // has different attributes), initialize expectations. + const int kSplitProp = kPropCount / 2; + Expectations expectations2(isolate); + + Handle map2 = initial_map; + for (int i = 0; i < kSplitProp; i++) { + map2 = expectations2.FollowDataTransition(map2, NONE, from_representation, + from_type); + } + map2 = + expectations2.AddDataField(map2, READ_ONLY, to_representation, to_type); + + for (int i = kSplitProp + 1; i < kPropCount; i++) { + map2 = expectations2.AddDataField(map2, NONE, to_representation, to_type); + } + CHECK(!map2->is_deprecated()); + CHECK(map2->is_stable()); + CHECK(expectations2.Check(*map2)); + + Zone zone; + FakeStubForTesting stub(isolate); + Handle field_owner(map->FindFieldOwner(kSplitProp), isolate); + CompilationInfo info(&stub, isolate, &zone); + CHECK(!info.HasAbortedDueToDependencyChange()); + Map::AddDependentCompilationInfo(field_owner, DependentCode::kFieldTypeGroup, + &info); + + // Reconfigure attributes of property |kSplitProp| of |map2| to NONE, which + // should generalize representations in |map1|. + Handle new_map = + Map::ReconfigureExistingProperty(map2, kSplitProp, kData, NONE); + + // |map2| should be left unchanged. + CHECK(!map2->is_deprecated()); + CHECK_NE(*map2, *new_map); + CHECK(expectations2.Check(*map2)); + + // |map| should be deprecated and |new_map| should match new expectations. + for (int i = kSplitProp; i < kPropCount; i++) { + expectations.SetDataField(i, expected_representation, expected_type); + } + CHECK(map->is_deprecated()); + CHECK(!info.HasAbortedDueToDependencyChange()); + info.RollbackDependencies(); // Properly cleanup compilation info. + CHECK_NE(*map, *new_map); + + CHECK(!new_map->is_deprecated()); + CHECK(!new_map->is_dictionary_map()); + CHECK(expectations.Check(*new_map)); + + // Update deprecated |map|, it should become |new_map|. + CHECK_EQ(*new_map, *Map::Update(map)); +} + + +// This test ensures that trivial representation/field type generalization +// (from HeapObject to HeapObject) is correctly propagated from one branch of +// transition tree (|map2|) to another (|map|). +// +// - p2B - p3 - p4: |map2| +// / +// {} - p0 - p1 - p2A - p3 - p4: |map| +// +// where "p2A" and "p2B" differ only in the attributes. +// +static void TestReconfigureDataFieldAttribute_GeneralizeRepresentationTrivial( + Representation from_representation, Handle from_type, + Representation to_representation, Handle to_type, + Representation expected_representation, Handle expected_type, + bool expected_field_type_dependency = true) { + Isolate* isolate = CcTest::i_isolate(); + + Expectations expectations(isolate); + + // Create a map, add required properties to it and initialize expectations. + Handle initial_map = Map::Create(isolate, 0); + Handle map = initial_map; + for (int i = 0; i < kPropCount; i++) { + map = expectations.AddDataField(map, NONE, from_representation, from_type); + } + CHECK(!map->is_deprecated()); + CHECK(map->is_stable()); + CHECK(expectations.Check(*map)); + + + // Create another branch in transition tree (property at index |kSplitProp| + // has different attributes), initialize expectations. + const int kSplitProp = kPropCount / 2; + Expectations expectations2(isolate); + + Handle map2 = initial_map; + for (int i = 0; i < kSplitProp; i++) { + map2 = expectations2.FollowDataTransition(map2, NONE, from_representation, + from_type); + } + map2 = + expectations2.AddDataField(map2, READ_ONLY, to_representation, to_type); + + for (int i = kSplitProp + 1; i < kPropCount; i++) { + map2 = expectations2.AddDataField(map2, NONE, to_representation, to_type); + } + CHECK(!map2->is_deprecated()); + CHECK(map2->is_stable()); + CHECK(expectations2.Check(*map2)); + + Zone zone; + FakeStubForTesting stub(isolate); + Handle field_owner(map->FindFieldOwner(kSplitProp), isolate); + CompilationInfo info(&stub, isolate, &zone); + CHECK(!info.HasAbortedDueToDependencyChange()); + Map::AddDependentCompilationInfo(field_owner, DependentCode::kFieldTypeGroup, + &info); + + // Reconfigure attributes of property |kSplitProp| of |map2| to NONE, which + // should generalize representations in |map1|. + Handle new_map = + Map::ReconfigureExistingProperty(map2, kSplitProp, kData, NONE); + + // |map2| should be left unchanged. + CHECK(!map2->is_deprecated()); + CHECK_NE(*map2, *new_map); + CHECK(expectations2.Check(*map2)); + + // In trivial case |map| should be returned as a result of the property + // reconfiguration, respective field types should be generalized and + // respective code dependencies should be invalidated. |map| should be NOT + // deprecated and it should match new expectations. + for (int i = kSplitProp; i < kPropCount; i++) { + expectations.SetDataField(i, expected_representation, expected_type); + } + CHECK(!map->is_deprecated()); + CHECK_EQ(*map, *new_map); + CHECK_EQ(expected_field_type_dependency, + info.HasAbortedDueToDependencyChange()); + info.RollbackDependencies(); // Properly cleanup compilation info. + + CHECK(!new_map->is_deprecated()); + CHECK(!new_map->is_dictionary_map()); + CHECK(expectations.Check(*new_map)); + + CHECK_EQ(*new_map, *Map::Update(map)); +} + + +TEST(ReconfigureDataFieldAttribute_GeneralizeRepresentationSmiToDouble) { + CcTest::InitializeVM(); + v8::HandleScope scope(CcTest::isolate()); + Isolate* isolate = CcTest::i_isolate(); + Handle any_type = HeapType::Any(isolate); + + TestReconfigureDataFieldAttribute_GeneralizeRepresentation( + Representation::Smi(), any_type, Representation::Double(), any_type, + Representation::Double(), any_type); +} + + +TEST(ReconfigureDataFieldAttribute_GeneralizeRepresentationSmiToTagged) { + CcTest::InitializeVM(); + v8::HandleScope scope(CcTest::isolate()); + Isolate* isolate = CcTest::i_isolate(); + Handle any_type = HeapType::Any(isolate); + Handle value_type = + HeapType::Class(Map::Create(isolate, 0), isolate); + + TestReconfigureDataFieldAttribute_GeneralizeRepresentation( + Representation::Smi(), any_type, Representation::HeapObject(), value_type, + Representation::Tagged(), any_type); +} + + +TEST(ReconfigureDataFieldAttribute_GeneralizeRepresentationDoubleToTagged) { + CcTest::InitializeVM(); + v8::HandleScope scope(CcTest::isolate()); + Isolate* isolate = CcTest::i_isolate(); + Handle any_type = HeapType::Any(isolate); + Handle value_type = + HeapType::Class(Map::Create(isolate, 0), isolate); + + TestReconfigureDataFieldAttribute_GeneralizeRepresentation( + Representation::Double(), any_type, Representation::HeapObject(), + value_type, Representation::Tagged(), any_type); +} + + +TEST(ReconfigureDataFieldAttribute_GeneralizeRepresentationHeapObjToHeapObj) { + CcTest::InitializeVM(); + v8::HandleScope scope(CcTest::isolate()); + Isolate* isolate = CcTest::i_isolate(); + Handle any_type = HeapType::Any(isolate); + + const int kMaxClassesPerFieldType = 5; + Handle current_type = + HeapType::Class(Map::Create(isolate, 0), isolate); + + for (int i = 0; i < kMaxClassesPerFieldType; i++) { + Handle new_type = + HeapType::Class(Map::Create(isolate, 0), isolate); + + Handle expected_type = + (i < kMaxClassesPerFieldType - 1) + ? HeapType::Union(current_type, new_type, isolate) + : any_type; + + TestReconfigureDataFieldAttribute_GeneralizeRepresentationTrivial( + Representation::HeapObject(), current_type, + Representation::HeapObject(), new_type, Representation::HeapObject(), + expected_type); + current_type = expected_type; + } + + Handle new_type = HeapType::Class(Map::Create(isolate, 0), isolate); + + TestReconfigureDataFieldAttribute_GeneralizeRepresentationTrivial( + Representation::HeapObject(), any_type, Representation::HeapObject(), + new_type, Representation::HeapObject(), any_type, false); +} + + +TEST(ReconfigureDataFieldAttribute_GeneralizeRepresentationHeapObjectToTagged) { + CcTest::InitializeVM(); + v8::HandleScope scope(CcTest::isolate()); + Isolate* isolate = CcTest::i_isolate(); + Handle any_type = HeapType::Any(isolate); + Handle value_type = + HeapType::Class(Map::Create(isolate, 0), isolate); + + TestReconfigureDataFieldAttribute_GeneralizeRepresentation( + Representation::HeapObject(), value_type, Representation::Smi(), any_type, + Representation::Tagged(), any_type); +} + + +// Checks that given |map| is deprecated and that it updates to given |new_map| +// which in turn should match expectations. +struct CheckDeprecated { + void Check(Handle map, Handle new_map, + const Expectations& expectations) { + CHECK(map->is_deprecated()); + CHECK_NE(*map, *new_map); + + CHECK(!new_map->is_deprecated()); + CHECK(!new_map->is_dictionary_map()); + CHECK(expectations.Check(*new_map)); + + // Update deprecated |map|, it should become |new_map|. + CHECK_EQ(*new_map, *Map::Update(map)); + } +}; + + +// Checks that given |map| is NOT deprecated, equals to given |new_map| and +// matches expectations. +struct CheckSameMap { + void Check(Handle map, Handle new_map, + const Expectations& expectations) { + CHECK(!map->is_deprecated()); + CHECK_EQ(*map, *new_map); + + CHECK(!new_map->is_deprecated()); + CHECK(!new_map->is_dictionary_map()); + CHECK(expectations.Check(*new_map)); + + // Update deprecated |map|, it should become |new_map|. + CHECK_EQ(*new_map, *Map::Update(map)); + } +}; + + +// Checks that given |map| is NOT deprecated, and |new_map| is a result of +// copy-generalize-all-representations. +struct CheckCopyGeneralizeAllRepresentations { + void Check(Handle map, Handle new_map, Expectations& expectations) { + CHECK(!map->is_deprecated()); + CHECK_NE(*map, *new_map); + + CHECK(new_map->GetBackPointer()->IsUndefined()); + for (int i = 0; i < kPropCount; i++) { + expectations.GeneralizeRepresentation(i); + } + + CHECK(!new_map->is_deprecated()); + CHECK(!new_map->is_dictionary_map()); + CHECK(expectations.Check(*new_map)); + } +}; + + +// This test ensures that representation/field type generalization is correctly +// propagated from one branch of transition tree (|map2|) to another (|map1|). +// +// - p2B - p3 - p4: |map2| +// / +// {} - p0 - p1: |map| +// \ +// - p2A - p3 - p4: |map1| +// \ +// - the property customized by the TestConfig provided +// +// where "p2A" and "p2B" differ only in the attributes. +// +template +static void TestReconfigureProperty_CustomPropertyAfterTargetMap( + TestConfig& config, Checker& checker) { + Isolate* isolate = CcTest::i_isolate(); + Handle any_type = HeapType::Any(isolate); + + const int kCustomPropIndex = kPropCount - 2; + Expectations expectations(isolate); + + const int kSplitProp = 2; + CHECK(kSplitProp < kCustomPropIndex); + + const Representation representation = Representation::Smi(); + + // Create common part of transition tree. + Handle initial_map = Map::Create(isolate, 0); + Handle map = initial_map; + for (int i = 0; i < kSplitProp; i++) { + map = expectations.AddDataField(map, NONE, representation, any_type); + } + CHECK(!map->is_deprecated()); + CHECK(map->is_stable()); + CHECK(expectations.Check(*map)); + + + // Create branch to |map1|. + Handle map1 = map; + Expectations expectations1 = expectations; + for (int i = kSplitProp; i < kCustomPropIndex; i++) { + map1 = expectations1.AddDataField(map1, NONE, representation, any_type); + } + map1 = config.AddPropertyAtBranch(1, expectations1, map1); + for (int i = kCustomPropIndex + 1; i < kPropCount; i++) { + map1 = expectations1.AddDataField(map1, NONE, representation, any_type); + } + CHECK(!map1->is_deprecated()); + CHECK(map1->is_stable()); + CHECK(expectations1.Check(*map1)); + + + // Create another branch in transition tree (property at index |kSplitProp| + // has different attributes), initialize expectations. + Handle map2 = map; + Expectations expectations2 = expectations; + map2 = expectations2.AddDataField(map2, READ_ONLY, representation, any_type); + for (int i = kSplitProp + 1; i < kCustomPropIndex; i++) { + map2 = expectations2.AddDataField(map2, NONE, representation, any_type); + } + map2 = config.AddPropertyAtBranch(2, expectations2, map2); + for (int i = kCustomPropIndex + 1; i < kPropCount; i++) { + map2 = expectations2.AddDataField(map2, NONE, representation, any_type); + } + CHECK(!map2->is_deprecated()); + CHECK(map2->is_stable()); + CHECK(expectations2.Check(*map2)); + + + // Reconfigure attributes of property |kSplitProp| of |map2| to NONE, which + // should generalize representations in |map1|. + Handle new_map = + Map::ReconfigureExistingProperty(map2, kSplitProp, kData, NONE); + + // |map2| should be left unchanged. + CHECK(!map2->is_deprecated()); + CHECK_NE(*map2, *new_map); + CHECK(expectations2.Check(*map2)); + + config.UpdateExpectations(kCustomPropIndex, expectations1); + checker.Check(map1, new_map, expectations1); +} + + +TEST(ReconfigureDataFieldAttribute_SameDataConstantAfterTargetMap) { + CcTest::InitializeVM(); + v8::HandleScope scope(CcTest::isolate()); + + struct TestConfig { + Handle js_func_; + TestConfig() { + Isolate* isolate = CcTest::i_isolate(); + Factory* factory = isolate->factory(); + js_func_ = factory->NewFunction(factory->empty_string()); + } + + Handle AddPropertyAtBranch(int branch_id, Expectations& expectations, + Handle map) { + CHECK(branch_id == 1 || branch_id == 2); + // Add the same data constant property at both transition tree branches. + return expectations.AddDataConstant(map, NONE, js_func_); + } + + void UpdateExpectations(int property_index, Expectations& expectations) { + // Expectations stay the same. + } + }; + + TestConfig config; + // Two branches are "compatible" so the |map1| should NOT be deprecated. + CheckSameMap checker; + TestReconfigureProperty_CustomPropertyAfterTargetMap(config, checker); +} + + +TEST(ReconfigureDataFieldAttribute_DataConstantToDataFieldAfterTargetMap) { + CcTest::InitializeVM(); + v8::HandleScope scope(CcTest::isolate()); + + struct TestConfig { + Handle js_func1_; + Handle js_func2_; + TestConfig() { + Isolate* isolate = CcTest::i_isolate(); + Factory* factory = isolate->factory(); + js_func1_ = factory->NewFunction(factory->empty_string()); + js_func2_ = factory->NewFunction(factory->empty_string()); + } + + Handle AddPropertyAtBranch(int branch_id, Expectations& expectations, + Handle map) { + CHECK(branch_id == 1 || branch_id == 2); + Handle js_func = branch_id == 1 ? js_func1_ : js_func2_; + return expectations.AddDataConstant(map, NONE, js_func); + } + + void UpdateExpectations(int property_index, Expectations& expectations) { + Isolate* isolate = CcTest::i_isolate(); + Handle any_type = HeapType::Any(isolate); + expectations.SetDataField(property_index, Representation::HeapObject(), + any_type); + } + }; + + TestConfig config; + // Two branches are "incompatible" so the |map1| should be deprecated. + CheckDeprecated checker; + TestReconfigureProperty_CustomPropertyAfterTargetMap(config, checker); +} + + +TEST(ReconfigureDataFieldAttribute_SameAccessorConstantAfterTargetMap) { + CcTest::InitializeVM(); + v8::HandleScope scope(CcTest::isolate()); + + struct TestConfig { + Handle pair_; + TestConfig() { pair_ = CreateAccessorPair(true, true); } + + Handle AddPropertyAtBranch(int branch_id, Expectations& expectations, + Handle map) { + CHECK(branch_id == 1 || branch_id == 2); + // Add the same accessor constant property at both transition tree + // branches. + return expectations.AddAccessorConstant(map, NONE, pair_); + } + + bool UpdateExpectations(int property_index, Expectations& expectations) { + // Two branches are "compatible" so the |map1| should NOT be deprecated. + return false; + } + }; + + TestConfig config; + CheckSameMap checker; + TestReconfigureProperty_CustomPropertyAfterTargetMap(config, checker); +} + + +TEST(ReconfigureDataFieldAttribute_AccConstantToAccFieldAfterTargetMap) { + CcTest::InitializeVM(); + v8::HandleScope scope(CcTest::isolate()); + + struct TestConfig { + Handle pair1_; + Handle pair2_; + TestConfig() { + pair1_ = CreateAccessorPair(true, true); + pair2_ = CreateAccessorPair(true, true); + } + + Handle AddPropertyAtBranch(int branch_id, Expectations& expectations, + Handle map) { + CHECK(branch_id == 1 || branch_id == 2); + Handle pair = branch_id == 1 ? pair1_ : pair2_; + return expectations.AddAccessorConstant(map, NONE, pair); + } + + void UpdateExpectations(int property_index, Expectations& expectations) { + if (IS_ACCESSOR_FIELD_SUPPORTED) { + expectations.SetAccessorField(property_index); + } else { + // Currently we have a copy-generalize-all-representations case and + // ACCESSOR property becomes ACCESSOR_CONSTANT. + expectations.SetAccessorConstant(property_index, pair2_); + } + } + }; + + TestConfig config; + if (IS_ACCESSOR_FIELD_SUPPORTED) { + CheckCopyGeneralizeAllRepresentations checker; + TestReconfigureProperty_CustomPropertyAfterTargetMap(config, checker); + } else { + // Currently we have a copy-generalize-all-representations case. + CheckCopyGeneralizeAllRepresentations checker; + TestReconfigureProperty_CustomPropertyAfterTargetMap(config, checker); + } +} + + +//////////////////////////////////////////////////////////////////////////////// +// A set of tests checking split map deprecation. +// + +TEST(ReconfigurePropertySplitMapTransitionsOverflow) { + CcTest::InitializeVM(); + v8::HandleScope scope(CcTest::isolate()); + Isolate* isolate = CcTest::i_isolate(); + Handle any_type = HeapType::Any(isolate); + + Expectations expectations(isolate); + + // Create a map, add required properties to it and initialize expectations. + Handle initial_map = Map::Create(isolate, 0); + Handle map = initial_map; + for (int i = 0; i < kPropCount; i++) { + map = expectations.AddDataField(map, NONE, Representation::Smi(), any_type); + } + CHECK(!map->is_deprecated()); + CHECK(map->is_stable()); + + // Generalize representation of property at index |kSplitProp|. + const int kSplitProp = kPropCount / 2; + Handle split_map; + Handle map2 = initial_map; + { + for (int i = 0; i < kSplitProp + 1; i++) { + if (i == kSplitProp) { + split_map = map2; + } + + Handle name = MakeName("prop", i); + int t = map2->SearchTransition(kData, *name, NONE); + CHECK_NE(TransitionArray::kNotFound, t); + map2 = handle(map2->GetTransition(t)); + } + + map2 = Map::ReconfigureProperty(map2, kSplitProp, kData, NONE, + Representation::Double(), any_type, + FORCE_FIELD); + expectations.SetDataField(kSplitProp, Representation::Double(), any_type); + + CHECK(expectations.Check(*split_map, kSplitProp)); + CHECK(expectations.Check(*map2, kSplitProp + 1)); + } + + // At this point |map| should be deprecated and disconnected from the + // transition tree. + CHECK(map->is_deprecated()); + CHECK(!split_map->is_deprecated()); + CHECK(!map2->is_deprecated()); + + // Fill in transition tree of |map2| so that it can't have more transitions. + for (int i = 0; i < TransitionArray::kMaxNumberOfTransitions; i++) { + CHECK(map2->CanHaveMoreTransitions()); + Handle name = MakeName("foo", i); + Map::CopyWithField(map2, name, any_type, NONE, Representation::Smi(), + INSERT_TRANSITION).ToHandleChecked(); + } + CHECK(!map2->CanHaveMoreTransitions()); + + // Try to update |map|, since there is no place for propX transition at |map2| + // |map| should become "copy-generalized". + Handle updated_map = Map::Update(map); + CHECK(updated_map->GetBackPointer()->IsUndefined()); + + for (int i = 0; i < kPropCount; i++) { + expectations.SetDataField(i, Representation::Tagged(), any_type); + } + CHECK(expectations.Check(*updated_map)); +} + + +//////////////////////////////////////////////////////////////////////////////// +// A set of tests involving special transitions (such as elements kind +// transition, observed transition or prototype transition). +// + +// This test ensures that representation/field type generalization is correctly +// propagated from one branch of transition tree (|map2|) to another (|map|). +// +// p4B: |map2| +// | +// * - special transition +// | +// {} - p0 - p1 - p2A - p3 - p4A: |map| +// +// where "p4A" and "p4B" are exactly the same properties. +// +// TODO(ishell): unify this test template with +// TestReconfigureDataFieldAttribute_GeneralizeRepresentation once +// IS_PROTO_TRANS_ISSUE_FIXED and IS_NON_EQUIVALENT_TRANSITION_SUPPORTED are +// fixed. +template +static void TestGeneralizeRepresentationWithSpecialTransition( + TestConfig& config, Representation from_representation, + Handle from_type, Representation to_representation, + Handle to_type, Representation expected_representation, + Handle expected_type) { + Isolate* isolate = CcTest::i_isolate(); + + Expectations expectations(isolate); + + // Create a map, add required properties to it and initialize expectations. + Handle initial_map = Map::Create(isolate, 0); + Handle map = initial_map; + for (int i = 0; i < kPropCount; i++) { + map = expectations.AddDataField(map, NONE, from_representation, from_type); + } + CHECK(!map->is_deprecated()); + CHECK(map->is_stable()); + CHECK(expectations.Check(*map)); + + // Apply some special transition to |map|. + CHECK(map->owns_descriptors()); + Handle map2 = config.Transition(map); + + // |map| should still match expectations. + CHECK(!map->is_deprecated()); + CHECK(expectations.Check(*map)); + + Expectations expectations2 = expectations; + if (config.generalizes_representations()) { + for (int i = 0; i < kPropCount; i++) { + expectations2.GeneralizeRepresentation(i); + } + } + + CHECK(!map2->is_deprecated()); + CHECK(map2->is_stable()); + CHECK(expectations2.Check(*map2)); + + // Create new maps by generalizing representation of propX field. + Handle maps[kPropCount]; + for (int i = 0; i < kPropCount; i++) { + Handle new_map = Map::ReconfigureProperty( + map, i, kData, NONE, to_representation, to_type, FORCE_FIELD); + maps[i] = new_map; + + expectations.SetDataField(i, expected_representation, expected_type); + + CHECK(map->is_deprecated()); + CHECK_NE(*map, *new_map); + CHECK(i == 0 || maps[i - 1]->is_deprecated()); + CHECK(expectations.Check(*new_map)); + + Handle new_map2 = Map::Update(map2); + CHECK(!new_map2->is_deprecated()); + CHECK(!new_map2->is_dictionary_map()); + + if (!IS_NON_EQUIVALENT_TRANSITION_SUPPORTED) { + // In case of non-equivalent transition currently we generalize all + // representations. + for (int i = 0; i < kPropCount; i++) { + expectations2.GeneralizeRepresentation(i); + } + CHECK(new_map2->GetBackPointer()->IsUndefined()); + CHECK(expectations2.Check(*new_map2)); + } else { + CHECK(expectations.Check(*new_map2)); + } + } + + Handle active_map = maps[kPropCount - 1]; + CHECK(!active_map->is_deprecated()); + + // Update all deprecated maps and check that they are now the same. + CHECK_EQ(*active_map, *Map::Update(map)); + for (int i = 0; i < kPropCount; i++) { + CHECK_EQ(*active_map, *Map::Update(maps[i])); + } +} + + +TEST(ElementsKindTransitionFromMapOwningDescriptor) { + CcTest::InitializeVM(); + v8::HandleScope scope(CcTest::isolate()); + Isolate* isolate = CcTest::i_isolate(); + Handle any_type = HeapType::Any(isolate); + Handle value_type = + HeapType::Class(Map::Create(isolate, 0), isolate); + + struct TestConfig { + Handle Transition(Handle map) { + return Map::CopyAsElementsKind(map, DICTIONARY_ELEMENTS, + INSERT_TRANSITION); + } + // TODO(ishell): remove once IS_PROTO_TRANS_ISSUE_FIXED is removed. + bool generalizes_representations() const { return false; } + }; + TestConfig config; + TestGeneralizeRepresentationWithSpecialTransition( + config, Representation::Smi(), any_type, Representation::HeapObject(), + value_type, Representation::Tagged(), any_type); +} + + +TEST(ElementsKindTransitionFromMapNotOwningDescriptor) { + CcTest::InitializeVM(); + v8::HandleScope scope(CcTest::isolate()); + Isolate* isolate = CcTest::i_isolate(); + Handle any_type = HeapType::Any(isolate); + Handle value_type = + HeapType::Class(Map::Create(isolate, 0), isolate); + + struct TestConfig { + Handle Transition(Handle map) { + Isolate* isolate = CcTest::i_isolate(); + Handle any_type = HeapType::Any(isolate); + + // Add one more transition to |map| in order to prevent descriptors + // ownership. + CHECK(map->owns_descriptors()); + Map::CopyWithField(map, MakeString("foo"), any_type, NONE, + Representation::Smi(), + INSERT_TRANSITION).ToHandleChecked(); + CHECK(!map->owns_descriptors()); + + return Map::CopyAsElementsKind(map, DICTIONARY_ELEMENTS, + INSERT_TRANSITION); + } + // TODO(ishell): remove once IS_PROTO_TRANS_ISSUE_FIXED is removed. + bool generalizes_representations() const { return false; } + }; + TestConfig config; + TestGeneralizeRepresentationWithSpecialTransition( + config, Representation::Smi(), any_type, Representation::HeapObject(), + value_type, Representation::Tagged(), any_type); +} + + +TEST(ForObservedTransitionFromMapOwningDescriptor) { + CcTest::InitializeVM(); + v8::HandleScope scope(CcTest::isolate()); + Isolate* isolate = CcTest::i_isolate(); + Handle any_type = HeapType::Any(isolate); + Handle value_type = + HeapType::Class(Map::Create(isolate, 0), isolate); + + struct TestConfig { + Handle Transition(Handle map) { + return Map::CopyForObserved(map); + } + // TODO(ishell): remove once IS_PROTO_TRANS_ISSUE_FIXED is removed. + bool generalizes_representations() const { return false; } + }; + TestConfig config; + TestGeneralizeRepresentationWithSpecialTransition( + config, Representation::Smi(), any_type, Representation::HeapObject(), + value_type, Representation::Tagged(), any_type); +} + + +TEST(ForObservedTransitionFromMapNotOwningDescriptor) { + CcTest::InitializeVM(); + v8::HandleScope scope(CcTest::isolate()); + Isolate* isolate = CcTest::i_isolate(); + Handle any_type = HeapType::Any(isolate); + Handle value_type = + HeapType::Class(Map::Create(isolate, 0), isolate); + + struct TestConfig { + Handle Transition(Handle map) { + Isolate* isolate = CcTest::i_isolate(); + Handle any_type = HeapType::Any(isolate); + + // Add one more transition to |map| in order to prevent descriptors + // ownership. + CHECK(map->owns_descriptors()); + Map::CopyWithField(map, MakeString("foo"), any_type, NONE, + Representation::Smi(), + INSERT_TRANSITION).ToHandleChecked(); + CHECK(!map->owns_descriptors()); + + return Map::CopyForObserved(map); + } + // TODO(ishell): remove once IS_PROTO_TRANS_ISSUE_FIXED is removed. + bool generalizes_representations() const { return false; } + }; + TestConfig config; + TestGeneralizeRepresentationWithSpecialTransition( + config, Representation::Smi(), any_type, Representation::HeapObject(), + value_type, Representation::Tagged(), any_type); +} + + +TEST(PrototypeTransitionFromMapOwningDescriptor) { + CcTest::InitializeVM(); + v8::HandleScope scope(CcTest::isolate()); + Isolate* isolate = CcTest::i_isolate(); + + Handle any_type = HeapType::Any(isolate); + Handle value_type = + HeapType::Class(Map::Create(isolate, 0), isolate); + + struct TestConfig { + Handle prototype_; + + TestConfig() { + Isolate* isolate = CcTest::i_isolate(); + Factory* factory = isolate->factory(); + prototype_ = factory->NewJSObjectFromMap(Map::Create(isolate, 0)); + } + + Handle Transition(Handle map) { + return Map::TransitionToPrototype(map, prototype_, REGULAR_PROTOTYPE); + } + // TODO(ishell): remove once IS_PROTO_TRANS_ISSUE_FIXED is removed. + bool generalizes_representations() const { + return !IS_PROTO_TRANS_ISSUE_FIXED; + } + }; + TestConfig config; + TestGeneralizeRepresentationWithSpecialTransition( + config, Representation::Smi(), any_type, Representation::HeapObject(), + value_type, Representation::Tagged(), any_type); +} + + +TEST(PrototypeTransitionFromMapNotOwningDescriptor) { + CcTest::InitializeVM(); + v8::HandleScope scope(CcTest::isolate()); + Isolate* isolate = CcTest::i_isolate(); + + Handle any_type = HeapType::Any(isolate); + Handle value_type = + HeapType::Class(Map::Create(isolate, 0), isolate); + + struct TestConfig { + Handle prototype_; + + TestConfig() { + Isolate* isolate = CcTest::i_isolate(); + Factory* factory = isolate->factory(); + prototype_ = factory->NewJSObjectFromMap(Map::Create(isolate, 0)); + } + + Handle Transition(Handle map) { + Isolate* isolate = CcTest::i_isolate(); + Handle any_type = HeapType::Any(isolate); + + // Add one more transition to |map| in order to prevent descriptors + // ownership. + CHECK(map->owns_descriptors()); + Map::CopyWithField(map, MakeString("foo"), any_type, NONE, + Representation::Smi(), + INSERT_TRANSITION).ToHandleChecked(); + CHECK(!map->owns_descriptors()); + + return Map::TransitionToPrototype(map, prototype_, REGULAR_PROTOTYPE); + } + // TODO(ishell): remove once IS_PROTO_TRANS_ISSUE_FIXED is removed. + bool generalizes_representations() const { + return !IS_PROTO_TRANS_ISSUE_FIXED; + } + }; + TestConfig config; + TestGeneralizeRepresentationWithSpecialTransition( + config, Representation::Smi(), any_type, Representation::HeapObject(), + value_type, Representation::Tagged(), any_type); +} + + +//////////////////////////////////////////////////////////////////////////////// +// A set of tests for higher level transitioning mechanics. +// + +struct TransitionToDataFieldOperator { + Representation representation_; + PropertyAttributes attributes_; + Handle heap_type_; + Handle value_; + + TransitionToDataFieldOperator(Representation representation, + Handle heap_type, + Handle value, + PropertyAttributes attributes = NONE) + : representation_(representation), + attributes_(attributes), + heap_type_(heap_type), + value_(value) {} + + Handle DoTransition(Expectations& expectations, Handle map) { + return expectations.TransitionToDataField(map, attributes_, representation_, + heap_type_, value_); + } +}; + + +struct TransitionToDataConstantOperator { + PropertyAttributes attributes_; + Handle value_; + + TransitionToDataConstantOperator(Handle value, + PropertyAttributes attributes = NONE) + : attributes_(attributes), value_(value) {} + + Handle DoTransition(Expectations& expectations, Handle map) { + return expectations.TransitionToDataConstant(map, attributes_, value_); + } +}; + + +struct TransitionToAccessorConstantOperator { + PropertyAttributes attributes_; + Handle pair_; + + TransitionToAccessorConstantOperator(Handle pair, + PropertyAttributes attributes = NONE) + : attributes_(attributes), pair_(pair) {} + + Handle DoTransition(Expectations& expectations, Handle map) { + return expectations.TransitionToAccessorConstant(map, attributes_, pair_); + } +}; + + +struct ReconfigureAsDataPropertyOperator { + int descriptor_; + Representation representation_; + PropertyAttributes attributes_; + Handle heap_type_; + + ReconfigureAsDataPropertyOperator(int descriptor, + Representation representation, + Handle heap_type, + PropertyAttributes attributes = NONE) + : descriptor_(descriptor), + representation_(representation), + attributes_(attributes), + heap_type_(heap_type) {} + + Handle DoTransition(Expectations& expectations, Handle map) { + expectations.SetDataField(descriptor_, representation_, heap_type_); + return Map::ReconfigureExistingProperty(map, descriptor_, kData, + attributes_); + } +}; + + +struct ReconfigureAsAccessorPropertyOperator { + int descriptor_; + PropertyAttributes attributes_; + + ReconfigureAsAccessorPropertyOperator(int descriptor, + PropertyAttributes attributes = NONE) + : descriptor_(descriptor), attributes_(attributes) {} + + Handle DoTransition(Expectations& expectations, Handle map) { + expectations.SetAccessorField(descriptor_); + return Map::ReconfigureExistingProperty(map, descriptor_, kAccessor, + attributes_); + } +}; + + +// Checks that representation/field type generalization happened. +struct FieldGeneralizationChecker { + int descriptor_; + Representation representation_; + PropertyAttributes attributes_; + Handle heap_type_; + + FieldGeneralizationChecker(int descriptor, Representation representation, + Handle heap_type, + PropertyAttributes attributes = NONE) + : descriptor_(descriptor), + representation_(representation), + attributes_(attributes), + heap_type_(heap_type) {} + + void Check(Expectations& expectations2, Handle map1, Handle map2) { + CHECK(!map2->is_deprecated()); + + CHECK(map1->is_deprecated()); + CHECK_NE(*map1, *map2); + CHECK_EQ(*map2, *Map::Update(map1)); + + expectations2.SetDataField(descriptor_, representation_, heap_type_); + CHECK(expectations2.Check(*map2)); + } +}; + + +// Checks that existing transition was taken as is. +struct SameMapChecker { + void Check(Expectations& expectations, Handle map1, Handle map2) { + CHECK(!map2->is_deprecated()); + CHECK_EQ(*map1, *map2); + CHECK(expectations.Check(*map2)); + } +}; + + +// Checks that both |map1| and |map2| should stays non-deprecated, this is +// the case when property kind is change. +struct PropertyKindReconfigurationChecker { + void Check(Expectations& expectations, Handle map1, Handle map2) { + CHECK(!map1->is_deprecated()); + CHECK(!map2->is_deprecated()); + CHECK_NE(*map1, *map2); + CHECK(expectations.Check(*map2)); + } +}; + + +// This test transitions to various property types under different +// circumstances. +// Plan: +// 1) create a |map| with p0..p3 properties. +// 2) create |map1| by adding "p4" to |map0|. +// 3) create |map2| by transition to "p4" from |map0|. +// +// - p4B: |map2| +// / +// {} - p0 - p1 - pA - p3: |map| +// \ +// - p4A: |map1| +// +// where "p4A" and "p4B" differ only in the attributes. +// +template +static void TestTransitionTo(TransitionOp1& transition_op1, + TransitionOp2& transition_op2, Checker& checker) { + Isolate* isolate = CcTest::i_isolate(); + Handle any_type = HeapType::Any(isolate); + + Expectations expectations(isolate); + + // Create a map, add required properties to it and initialize expectations. + Handle initial_map = Map::Create(isolate, 0); + Handle map = initial_map; + for (int i = 0; i < kPropCount - 1; i++) { + map = expectations.AddDataField(map, NONE, Representation::Smi(), any_type); + } + CHECK(expectations.Check(*map)); + + Expectations expectations1 = expectations; + Handle map1 = transition_op1.DoTransition(expectations1, map); + CHECK(expectations1.Check(*map1)); + + Expectations expectations2 = expectations; + Handle map2 = transition_op2.DoTransition(expectations2, map); + + // Let the test customization do the check. + checker.Check(expectations2, map1, map2); +} + + +TEST(TransitionDataFieldToDataField) { + CcTest::InitializeVM(); + v8::HandleScope scope(CcTest::isolate()); + Isolate* isolate = CcTest::i_isolate(); + Handle any_type = HeapType::Any(isolate); + + Handle value1 = handle(Smi::FromInt(0), isolate); + TransitionToDataFieldOperator transition_op1(Representation::Smi(), any_type, + value1); + + Handle value2 = isolate->factory()->NewHeapNumber(0); + TransitionToDataFieldOperator transition_op2(Representation::Double(), + any_type, value2); + + FieldGeneralizationChecker checker(kPropCount - 1, Representation::Double(), + any_type); + TestTransitionTo(transition_op1, transition_op2, checker); +} + + +TEST(TransitionDataConstantToSameDataConstant) { + CcTest::InitializeVM(); + v8::HandleScope scope(CcTest::isolate()); + Isolate* isolate = CcTest::i_isolate(); + Factory* factory = isolate->factory(); + + Handle js_func = factory->NewFunction(factory->empty_string()); + TransitionToDataConstantOperator transition_op(js_func); + + SameMapChecker checker; + TestTransitionTo(transition_op, transition_op, checker); +} + + +TEST(TransitionDataConstantToAnotherDataConstant) { + CcTest::InitializeVM(); + v8::HandleScope scope(CcTest::isolate()); + Isolate* isolate = CcTest::i_isolate(); + Factory* factory = isolate->factory(); + Handle any_type = HeapType::Any(isolate); + + Handle js_func1 = factory->NewFunction(factory->empty_string()); + TransitionToDataConstantOperator transition_op1(js_func1); + + Handle js_func2 = factory->NewFunction(factory->empty_string()); + TransitionToDataConstantOperator transition_op2(js_func2); + + FieldGeneralizationChecker checker(kPropCount - 1, + Representation::HeapObject(), any_type); + TestTransitionTo(transition_op1, transition_op2, checker); +} + + +TEST(TransitionDataConstantToDataField) { + CcTest::InitializeVM(); + v8::HandleScope scope(CcTest::isolate()); + Isolate* isolate = CcTest::i_isolate(); + Factory* factory = isolate->factory(); + Handle any_type = HeapType::Any(isolate); + + Handle js_func1 = factory->NewFunction(factory->empty_string()); + TransitionToDataConstantOperator transition_op1(js_func1); + + Handle value2 = isolate->factory()->NewHeapNumber(0); + TransitionToDataFieldOperator transition_op2(Representation::Double(), + any_type, value2); + + FieldGeneralizationChecker checker(kPropCount - 1, Representation::Tagged(), + any_type); + TestTransitionTo(transition_op1, transition_op2, checker); +} + + +TEST(TransitionAccessorConstantToSameAccessorConstant) { + CcTest::InitializeVM(); + v8::HandleScope scope(CcTest::isolate()); + + Handle pair = CreateAccessorPair(true, true); + TransitionToAccessorConstantOperator transition_op(pair); + + SameMapChecker checker; + TestTransitionTo(transition_op, transition_op, checker); +} + + +// TODO(ishell): add this test once IS_ACCESSOR_FIELD_SUPPORTED is supported. +// TEST(TransitionAccessorConstantToAnotherAccessorConstant) -- 2.7.4