// 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 "test/cctest/test-api.h" #include "src/v8.h" #include "src/compilation-cache.h" #include "src/compiler/compilation-dependencies.h" #include "src/compiler/js-heap-broker.h" #include "src/execution.h" #include "src/field-type.h" #include "src/global-handles.h" #include "src/heap/factory.h" #include "src/ic/stub-cache.h" #include "src/macro-assembler.h" #include "src/objects-inl.h" #include "src/optimized-compilation-info.h" #include "src/property.h" #include "src/transitions.h" namespace v8 { namespace internal { namespace compiler { namespace test_field_type_tracking { // 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 fields). 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(); Handle empty_string = factory->empty_string(); if (with_getter) { Handle func = factory->NewFunctionForTest(empty_string); pair->set_getter(*func); } if (with_setter) { Handle func = factory->NewFunctionForTest(empty_string); pair->set_setter(*func); } return pair; } class Expectations { static const int MAX_PROPERTIES = 10; Isolate* isolate_; ElementsKind elements_kind_; PropertyKind kinds_[MAX_PROPERTIES]; PropertyLocation locations_[MAX_PROPERTIES]; PropertyConstness constnesses_[MAX_PROPERTIES]; PropertyAttributes attributes_[MAX_PROPERTIES]; Representation representations_[MAX_PROPERTIES]; // FieldType 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, ElementsKind elements_kind) : isolate_(isolate), elements_kind_(elements_kind), number_of_properties_(0) {} explicit Expectations(Isolate* isolate) : Expectations( isolate, isolate->object_function()->initial_map()->elements_kind()) {} void Init(int index, PropertyKind kind, PropertyAttributes attributes, PropertyConstness constness, PropertyLocation location, Representation representation, Handle value) { CHECK(index < MAX_PROPERTIES); kinds_[index] = kind; locations_[index] = location; if (kind == kData && location == kField && IsTransitionableFastElementsKind(elements_kind_) && Map::IsInplaceGeneralizableField(constness, representation, FieldType::cast(*value))) { // Maps with transitionable elements kinds must have non in-place // generalizable fields. if (FLAG_track_constant_fields && FLAG_modify_map_inplace && constness == PropertyConstness::kConst) { constness = PropertyConstness::kMutable; } if (representation.IsHeapObject() && !FieldType::cast(*value)->IsAny()) { value = FieldType::Any(isolate_); } } constnesses_[index] = constness; attributes_[index] = attributes; representations_[index] = representation; values_[index] = value; } void Print() const { StdoutStream os; os << "Expectations: #" << number_of_properties_ << "\n"; for (int i = 0; i < number_of_properties_; i++) { os << " " << i << ": "; os << "Descriptor @ "; if (kinds_[i] == kData) { os << Brief(*values_[i]); } else { // kAccessor os << "(get: " << Brief(*values_[i]) << ", set: " << Brief(*setter_values_[i]) << ") "; } os << " ("; if (constnesses_[i] == PropertyConstness::kConst) os << "const "; os << (kinds_[i] == kData ? "data " : "accessor "); if (locations_[i] == kField) { os << "field" << ": " << representations_[i].Mnemonic(); } else { os << "descriptor"; } os << ", attrs: " << attributes_[i] << ")\n"; } os << "\n"; } void SetElementsKind(ElementsKind elements_kind) { elements_kind_ = elements_kind; } Handle GetFieldType(int index) { CHECK(index < MAX_PROPERTIES); CHECK_EQ(kField, locations_[index]); return Handle::cast(values_[index]); } void SetDataField(int index, PropertyAttributes attrs, PropertyConstness constness, Representation representation, Handle field_type) { Init(index, kData, attrs, constness, kField, representation, field_type); } void SetDataField(int index, PropertyConstness constness, Representation representation, Handle field_type) { SetDataField(index, attributes_[index], constness, representation, field_type); } void SetAccessorField(int index, PropertyAttributes attrs) { Init(index, kAccessor, attrs, PropertyConstness::kConst, kDescriptor, Representation::Tagged(), FieldType::Any(isolate_)); } void SetAccessorField(int index) { SetAccessorField(index, attributes_[index]); } void SetDataConstant(int index, PropertyAttributes attrs, Handle value) { if (FLAG_track_constant_fields) { Handle field_type(FieldType::Class(value->map()), isolate_); Init(index, kData, attrs, PropertyConstness::kConst, kField, Representation::HeapObject(), field_type); } else { Init(index, kData, attrs, PropertyConstness::kConst, kDescriptor, 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, kAccessor, attrs, PropertyConstness::kConst, kDescriptor, Representation::Tagged(), getter); setter_values_[index] = setter; } void SetAccessorConstantComponent(int index, PropertyAttributes attrs, AccessorComponent component, Handle accessor) { CHECK_EQ(kAccessor, kinds_[index]); CHECK_EQ(kDescriptor, locations_[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 GeneralizeField(int index) { CHECK(index < number_of_properties_); representations_[index] = Representation::Tagged(); if (locations_[index] == kField) { values_[index] = FieldType::Any(isolate_); } } bool Check(DescriptorArray* descriptors, int descriptor) const { PropertyDetails details = descriptors->GetDetails(descriptor); if (details.kind() != kinds_[descriptor]) return false; if (details.location() != locations_[descriptor]) return false; if (details.constness() != constnesses_[descriptor]) return false; PropertyAttributes expected_attributes = attributes_[descriptor]; if (details.attributes() != expected_attributes) return false; Representation expected_representation = representations_[descriptor]; if (!details.representation().Equals(expected_representation)) return false; Object* expected_value = *values_[descriptor]; if (details.location() == kField) { if (details.kind() == kData) { FieldType* type = descriptors->GetFieldType(descriptor); return FieldType::cast(expected_value) == type; } else { // kAccessor UNREACHABLE(); } } else { Object* value = descriptors->GetStrongValue(descriptor); // kDescriptor if (details.kind() == kData) { CHECK(!FLAG_track_constant_fields); return value == expected_value; } else { // kAccessor 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(); } bool Check(Map* map, int expected_nof) const { CHECK_EQ(elements_kind_, map->elements_kind()); 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(); #ifdef OBJECT_PRINT descriptors->Print(); #endif 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 AsElementsKind(Handle map, ElementsKind elements_kind) { elements_kind_ = elements_kind; map = Map::AsElementsKind(isolate_, map, elements_kind); CHECK_EQ(elements_kind_, map->elements_kind()); return map; } Handle AddDataField(Handle map, PropertyAttributes attributes, PropertyConstness constness, Representation representation, Handle field_type) { CHECK_EQ(number_of_properties_, map->NumberOfOwnDescriptors()); int property_index = number_of_properties_++; SetDataField(property_index, attributes, constness, representation, field_type); Handle name = MakeName("prop", property_index); return Map::CopyWithField(isolate_, map, name, field_type, attributes, constness, 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(isolate_, map, name, value, attributes, INSERT_TRANSITION) .ToHandleChecked(); } Handle TransitionToDataField(Handle map, PropertyAttributes attributes, PropertyConstness constness, Representation representation, Handle heap_type, Handle value) { CHECK_EQ(number_of_properties_, map->NumberOfOwnDescriptors()); int property_index = number_of_properties_++; SetDataField(property_index, attributes, constness, representation, heap_type); Handle name = MakeName("prop", property_index); return Map::TransitionToDataProperty(isolate_, map, name, value, attributes, constness, StoreOrigin::kNamed); } 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(isolate_, map, name, value, attributes, PropertyConstness::kConst, StoreOrigin::kNamed); } Handle FollowDataTransition(Handle map, PropertyAttributes attributes, PropertyConstness constness, Representation representation, Handle heap_type) { CHECK_EQ(number_of_properties_, map->NumberOfOwnDescriptors()); int property_index = number_of_properties_++; SetDataField(property_index, attributes, constness, representation, heap_type); Handle name = MakeName("prop", property_index); Map* target = TransitionsAccessor(isolate_, map) .SearchTransition(*name, kData, attributes); CHECK_NOT_NULL(target); return handle(target, isolate_); } 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); Descriptor d = Descriptor::AccessorConstant(name, pair, attributes); return Map::CopyInsertDescriptor(isolate_, map, &d, 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(isolate_) || !setter->IsNull(isolate_)); Factory* factory = isolate_->factory(); if (!getter->IsNull(isolate_)) { Handle pair = factory->NewAccessorPair(); pair->SetComponents(*getter, *factory->null_value()); Descriptor d = Descriptor::AccessorConstant(name, pair, attributes); map = Map::CopyInsertDescriptor(isolate_, map, &d, INSERT_TRANSITION); } if (!setter->IsNull(isolate_)) { Handle pair = factory->NewAccessorPair(); pair->SetComponents(*getter, *setter); Descriptor d = Descriptor::AccessorConstant(name, pair, attributes); map = Map::CopyInsertDescriptor(isolate_, map, &d, 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); int descriptor = map->instance_descriptors()->SearchWithCache(isolate, *name, *map); map = Map::TransitionToAccessorProperty(isolate, map, name, descriptor, getter, 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 = FieldType::Any(isolate); Handle none_type = FieldType::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( isolate, map, 0, kData, NONE, Representation::None(), none_type); // |map| did not change except marked unstable. CHECK(!map->is_deprecated()); CHECK(!map->is_stable()); CHECK(expectations.Check(*map)); // Property kind reconfiguration always makes the field mutable. expectations.SetDataField(0, NONE, PropertyConstness::kMutable, Representation::None(), none_type); CHECK(!new_map->is_deprecated()); CHECK(new_map->is_stable()); CHECK(expectations.Check(*new_map)); Handle new_map2 = Map::ReconfigureProperty( isolate, map, 0, kData, NONE, Representation::None(), none_type); CHECK_EQ(*new_map, *new_map2); Handle value(Smi::kZero, isolate); Handle prepared_map = Map::PrepareForDataProperty( isolate, new_map, 0, PropertyConstness::kConst, value); // None to Smi generalization is trivial, map does not change. CHECK_EQ(*new_map, *prepared_map); expectations.SetDataField(0, NONE, PropertyConstness::kMutable, 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(isolate)); #ifdef VERIFY_HEAP obj->ObjectVerify(isolate); #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(); v8::HandleScope scope(CcTest::isolate()); Isolate* isolate = CcTest::i_isolate(); Factory* factory = isolate->factory(); 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, 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()->GetStrongValue(0)->IsAccessorPair()); Handle value(Smi::FromInt(42), isolate); JSObject::SetOwnPropertyIgnoreAttributes(obj, foo_str, value, NONE).Check(); // 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::ToInt(the_value)); } //////////////////////////////////////////////////////////////////////////////// // A set of tests for field generalization case. // // data. struct CRFTData { PropertyConstness constness; Representation representation; Handle type; }; // This test ensures that field generalization at |property_index| is done // correctly independently of the fact that the |map| is detached from // transition tree or not. // // {} - p0 - p1 - p2: |detach_point_map| // | // X - detached at |detach_property_at_index| // | // + - p3 - p4: |map| // // Detaching does not happen if |detach_property_at_index| is -1. // static void TestGeneralizeField(int detach_property_at_index, int property_index, const CRFTData& from, const CRFTData& to, const CRFTData& expected, bool expected_deprecation, bool expected_field_type_dependency) { Isolate* isolate = CcTest::i_isolate(); Handle any_type = FieldType::Any(isolate); CHECK(detach_property_at_index >= -1 && detach_property_at_index < kPropCount); CHECK_LT(property_index, kPropCount); CHECK_NE(detach_property_at_index, property_index); const bool is_detached_map = detach_property_at_index >= 0; 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; Handle detach_point_map; for (int i = 0; i < kPropCount; i++) { if (i == property_index) { map = expectations.AddDataField(map, NONE, from.constness, from.representation, from.type); } else { map = expectations.AddDataField(map, NONE, kDefaultFieldConstness, Representation::Smi(), any_type); if (i == detach_property_at_index) { detach_point_map = map; } } } CHECK(!map->is_deprecated()); CHECK(map->is_stable()); CHECK(expectations.Check(*map)); Zone zone(isolate->allocator(), ZONE_NAME); if (is_detached_map) { detach_point_map = Map::ReconfigureProperty( isolate, detach_point_map, detach_property_at_index, kData, NONE, Representation::Tagged(), any_type); expectations.SetDataField(detach_property_at_index, kDefaultFieldConstness, Representation::Tagged(), any_type); CHECK(map->is_deprecated()); CHECK(expectations.Check(*detach_point_map, detach_point_map->NumberOfOwnDescriptors())); } // Create new maps by generalizing representation of propX field. CanonicalHandleScope canonical(isolate); JSHeapBroker broker(isolate, &zone); CompilationDependencies dependencies(isolate, &zone); MapRef map_ref(&broker, map); map_ref.SerializeOwnDescriptors(); dependencies.DependOnFieldType(map_ref, property_index); Handle field_owner(map->FindFieldOwner(isolate, property_index), isolate); Handle new_map = Map::ReconfigureProperty( isolate, map, property_index, kData, NONE, to.representation, to.type); expectations.SetDataField(property_index, expected.constness, expected.representation, expected.type); CHECK(!new_map->is_deprecated()); CHECK(expectations.Check(*new_map)); if (is_detached_map) { CHECK(!map->is_stable()); CHECK(map->is_deprecated()); CHECK_NE(*map, *new_map); CHECK_EQ(expected_field_type_dependency && !field_owner->is_deprecated(), !dependencies.AreValid()); } else if (expected_deprecation) { CHECK(!map->is_stable()); CHECK(map->is_deprecated()); CHECK(field_owner->is_deprecated()); CHECK_NE(*map, *new_map); CHECK(dependencies.AreValid()); } else { CHECK(!field_owner->is_deprecated()); CHECK(map->is_stable()); // Map did not change, must be left stable. CHECK_EQ(*map, *new_map); CHECK_EQ(expected_field_type_dependency, !dependencies.AreValid()); } { // Check that all previous maps are not stable. Map* tmp = *new_map; while (true) { Object* back = tmp->GetBackPointer(); if (back->IsUndefined(isolate)) break; tmp = Map::cast(back); CHECK(!tmp->is_stable()); } } // Update all deprecated maps and check that they are now the same. Handle updated_map = Map::Update(isolate, map); CHECK_EQ(*new_map, *updated_map); } static void TestGeneralizeField(const CRFTData& from, const CRFTData& to, const CRFTData& expected, bool expected_deprecation, bool expected_field_type_dependency) { // Check the cases when the map being reconfigured is a part of the // transition tree. STATIC_ASSERT(kPropCount > 4); int indices[] = {0, 2, kPropCount - 1}; for (int i = 0; i < static_cast(arraysize(indices)); i++) { TestGeneralizeField(-1, indices[i], from, to, expected, expected_deprecation, expected_field_type_dependency); } if (!from.representation.IsNone()) { // Check the cases when the map being reconfigured is NOT a part of the // transition tree. "None -> anything" representation changes make sense // only for "attached" maps. int indices[] = {0, kPropCount - 1}; for (int i = 0; i < static_cast(arraysize(indices)); i++) { TestGeneralizeField(indices[i], 2, from, to, expected, expected_deprecation, expected_field_type_dependency); } // Check that reconfiguration to the very same field works correctly. CRFTData data = from; TestGeneralizeField(-1, 2, data, data, data, false, false); } } static void TestGeneralizeField(const CRFTData& from, const CRFTData& to, const CRFTData& expected) { const bool expected_deprecation = true; const bool expected_field_type_dependency = false; TestGeneralizeField(from, to, expected, expected_deprecation, expected_field_type_dependency); } static void TestGeneralizeFieldTrivial( const CRFTData& from, const CRFTData& to, const CRFTData& expected, bool expected_field_type_dependency = true) { const bool expected_deprecation = false; TestGeneralizeField(from, to, expected, expected_deprecation, expected_field_type_dependency); } TEST(GeneralizeSmiFieldToDouble) { CcTest::InitializeVM(); v8::HandleScope scope(CcTest::isolate()); Isolate* isolate = CcTest::i_isolate(); Handle any_type = FieldType::Any(isolate); TestGeneralizeField( {PropertyConstness::kMutable, Representation::Smi(), any_type}, {PropertyConstness::kMutable, Representation::Double(), any_type}, {PropertyConstness::kMutable, Representation::Double(), any_type}); } TEST(GeneralizeSmiFieldToTagged) { CcTest::InitializeVM(); v8::HandleScope scope(CcTest::isolate()); Isolate* isolate = CcTest::i_isolate(); Handle any_type = FieldType::Any(isolate); Handle value_type = FieldType::Class(Map::Create(isolate, 0), isolate); TestGeneralizeField( {PropertyConstness::kMutable, Representation::Smi(), any_type}, {PropertyConstness::kMutable, Representation::HeapObject(), value_type}, {PropertyConstness::kMutable, Representation::Tagged(), any_type}); } TEST(GeneralizeDoubleFieldToTagged) { CcTest::InitializeVM(); v8::HandleScope scope(CcTest::isolate()); Isolate* isolate = CcTest::i_isolate(); Handle any_type = FieldType::Any(isolate); Handle value_type = FieldType::Class(Map::Create(isolate, 0), isolate); TestGeneralizeField( {PropertyConstness::kMutable, Representation::Double(), any_type}, {PropertyConstness::kMutable, Representation::HeapObject(), value_type}, {PropertyConstness::kMutable, Representation::Tagged(), any_type}); } TEST(GeneralizeHeapObjectFieldToTagged) { CcTest::InitializeVM(); v8::HandleScope scope(CcTest::isolate()); Isolate* isolate = CcTest::i_isolate(); Handle any_type = FieldType::Any(isolate); Handle value_type = FieldType::Class(Map::Create(isolate, 0), isolate); TestGeneralizeField( {PropertyConstness::kMutable, Representation::HeapObject(), value_type}, {PropertyConstness::kMutable, Representation::Smi(), any_type}, {PropertyConstness::kMutable, Representation::Tagged(), any_type}); } TEST(GeneralizeHeapObjectFieldToHeapObject) { CcTest::InitializeVM(); v8::HandleScope scope(CcTest::isolate()); Isolate* isolate = CcTest::i_isolate(); Handle any_type = FieldType::Any(isolate); Handle current_type = FieldType::Class(Map::Create(isolate, 0), isolate); Handle new_type = FieldType::Class(Map::Create(isolate, 0), isolate); Handle expected_type = any_type; TestGeneralizeFieldTrivial( {PropertyConstness::kMutable, Representation::HeapObject(), current_type}, {PropertyConstness::kMutable, Representation::HeapObject(), new_type}, {PropertyConstness::kMutable, Representation::HeapObject(), expected_type}); current_type = expected_type; new_type = FieldType::Class(Map::Create(isolate, 0), isolate); TestGeneralizeFieldTrivial( {PropertyConstness::kMutable, Representation::HeapObject(), any_type}, {PropertyConstness::kMutable, Representation::HeapObject(), new_type}, {PropertyConstness::kMutable, Representation::HeapObject(), any_type}, false); } TEST(GeneralizeNoneFieldToSmi) { CcTest::InitializeVM(); v8::HandleScope scope(CcTest::isolate()); Isolate* isolate = CcTest::i_isolate(); Handle none_type = FieldType::None(isolate); Handle any_type = FieldType::Any(isolate); // None -> Smi representation change is trivial. TestGeneralizeFieldTrivial( {PropertyConstness::kMutable, Representation::None(), none_type}, {PropertyConstness::kMutable, Representation::Smi(), any_type}, {PropertyConstness::kMutable, Representation::Smi(), any_type}); } TEST(GeneralizeNoneFieldToDouble) { CcTest::InitializeVM(); v8::HandleScope scope(CcTest::isolate()); Isolate* isolate = CcTest::i_isolate(); Handle none_type = FieldType::None(isolate); Handle any_type = FieldType::Any(isolate); // None -> Double representation change is NOT trivial. TestGeneralizeField( {PropertyConstness::kMutable, Representation::None(), none_type}, {PropertyConstness::kMutable, Representation::Double(), any_type}, {PropertyConstness::kMutable, Representation::Double(), any_type}); } TEST(GeneralizeNoneFieldToHeapObject) { CcTest::InitializeVM(); v8::HandleScope scope(CcTest::isolate()); Isolate* isolate = CcTest::i_isolate(); Handle none_type = FieldType::None(isolate); Handle value_type = FieldType::Class(Map::Create(isolate, 0), isolate); // None -> HeapObject representation change is trivial. TestGeneralizeFieldTrivial( {PropertyConstness::kMutable, Representation::None(), none_type}, {PropertyConstness::kMutable, Representation::HeapObject(), value_type}, {PropertyConstness::kMutable, Representation::HeapObject(), value_type}); } TEST(GeneralizeNoneFieldToTagged) { CcTest::InitializeVM(); v8::HandleScope scope(CcTest::isolate()); Isolate* isolate = CcTest::i_isolate(); Handle none_type = FieldType::None(isolate); Handle any_type = FieldType::Any(isolate); // None -> HeapObject representation change is trivial. TestGeneralizeFieldTrivial( {PropertyConstness::kMutable, Representation::None(), none_type}, {PropertyConstness::kMutable, Representation::Tagged(), any_type}, {PropertyConstness::kMutable, Representation::Tagged(), any_type}); } //////////////////////////////////////////////////////////////////////////////// // A set of tests for field generalization case with kAccessor properties. // TEST(GeneralizeFieldWithAccessorProperties) { CcTest::InitializeVM(); v8::HandleScope scope(CcTest::isolate()); Isolate* isolate = CcTest::i_isolate(); Handle any_type = FieldType::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, PropertyConstness::kMutable, 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( isolate, map, i, kData, NONE, Representation::Double(), any_type); maps[i] = new_map; expectations.SetDataField(i, PropertyConstness::kMutable, Representation::Double(), any_type); CHECK(!map->is_stable()); CHECK(map->is_deprecated()); CHECK_NE(*map, *new_map); CHECK(i == 0 || maps[i - 1]->is_deprecated()); CHECK(!new_map->is_deprecated()); 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. Handle updated_map = Map::Update(isolate, map); CHECK_EQ(*active_map, *updated_map); for (int i = 0; i < kPropCount; i++) { updated_map = Map::Update(isolate, maps[i]); CHECK_EQ(*active_map, *updated_map); } } //////////////////////////////////////////////////////////////////////////////// // A set of tests for attribute reconfiguration case. // // This test ensures that field 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_GeneralizeField( const CRFTData& from, const CRFTData& to, const CRFTData& expected) { 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.constness, 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.constness, from.representation, from.type); } map2 = expectations2.AddDataField(map2, READ_ONLY, to.constness, to.representation, to.type); for (int i = kSplitProp + 1; i < kPropCount; i++) { map2 = expectations2.AddDataField(map2, NONE, to.constness, to.representation, to.type); } CHECK(!map2->is_deprecated()); CHECK(map2->is_stable()); CHECK(expectations2.Check(*map2)); Zone zone(isolate->allocator(), ZONE_NAME); CanonicalHandleScope canonical(isolate); JSHeapBroker broker(isolate, &zone); CompilationDependencies dependencies(isolate, &zone); MapRef map_ref(&broker, map); map_ref.SerializeOwnDescriptors(); dependencies.DependOnFieldType(map_ref, kSplitProp); // Reconfigure attributes of property |kSplitProp| of |map2| to NONE, which // should generalize representations in |map1|. Handle new_map = Map::ReconfigureExistingProperty(isolate, map2, kSplitProp, kData, NONE); // |map2| should be left unchanged but marked unstable. CHECK(!map2->is_stable()); 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.constness, expected.representation, expected.type); } CHECK(map->is_deprecated()); CHECK(dependencies.AreValid()); CHECK_NE(*map, *new_map); CHECK(!new_map->is_deprecated()); CHECK(expectations.Check(*new_map)); // Update deprecated |map|, it should become |new_map|. Handle updated_map = Map::Update(isolate, map); CHECK_EQ(*new_map, *updated_map); } // This test ensures that trivial field 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_GeneralizeFieldTrivial( const CRFTData& from, const CRFTData& to, const CRFTData& expected, 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.constness, 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.constness, from.representation, from.type); } map2 = expectations2.AddDataField(map2, READ_ONLY, to.constness, to.representation, to.type); for (int i = kSplitProp + 1; i < kPropCount; i++) { map2 = expectations2.AddDataField(map2, NONE, to.constness, to.representation, to.type); } CHECK(!map2->is_deprecated()); CHECK(map2->is_stable()); CHECK(expectations2.Check(*map2)); Zone zone(isolate->allocator(), ZONE_NAME); CanonicalHandleScope canonical(isolate); JSHeapBroker broker(isolate, &zone); CompilationDependencies dependencies(isolate, &zone); MapRef map_ref(&broker, map); map_ref.SerializeOwnDescriptors(); dependencies.DependOnFieldType(map_ref, kSplitProp); // Reconfigure attributes of property |kSplitProp| of |map2| to NONE, which // should generalize representations in |map1|. Handle new_map = Map::ReconfigureExistingProperty(isolate, map2, kSplitProp, kData, NONE); // |map2| should be left unchanged but marked unstable. CHECK(!map2->is_stable()); 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.constness, expected.representation, expected.type); } CHECK(!map->is_deprecated()); CHECK_EQ(*map, *new_map); CHECK_EQ(expected_field_type_dependency, !dependencies.AreValid()); CHECK(!new_map->is_deprecated()); CHECK(expectations.Check(*new_map)); Handle updated_map = Map::Update(isolate, map); CHECK_EQ(*new_map, *updated_map); } TEST(ReconfigureDataFieldAttribute_GeneralizeSmiFieldToDouble) { CcTest::InitializeVM(); v8::HandleScope scope(CcTest::isolate()); Isolate* isolate = CcTest::i_isolate(); Handle any_type = FieldType::Any(isolate); if (FLAG_track_constant_fields) { TestReconfigureDataFieldAttribute_GeneralizeField( {PropertyConstness::kConst, Representation::Smi(), any_type}, {PropertyConstness::kConst, Representation::Double(), any_type}, {PropertyConstness::kConst, Representation::Double(), any_type}); TestReconfigureDataFieldAttribute_GeneralizeField( {PropertyConstness::kConst, Representation::Smi(), any_type}, {PropertyConstness::kMutable, Representation::Double(), any_type}, {PropertyConstness::kMutable, Representation::Double(), any_type}); TestReconfigureDataFieldAttribute_GeneralizeField( {PropertyConstness::kMutable, Representation::Smi(), any_type}, {PropertyConstness::kConst, Representation::Double(), any_type}, {PropertyConstness::kMutable, Representation::Double(), any_type}); } TestReconfigureDataFieldAttribute_GeneralizeField( {PropertyConstness::kMutable, Representation::Smi(), any_type}, {PropertyConstness::kMutable, Representation::Double(), any_type}, {PropertyConstness::kMutable, Representation::Double(), any_type}); } TEST(ReconfigureDataFieldAttribute_GeneralizeSmiFieldToTagged) { CcTest::InitializeVM(); v8::HandleScope scope(CcTest::isolate()); Isolate* isolate = CcTest::i_isolate(); Handle any_type = FieldType::Any(isolate); Handle value_type = FieldType::Class(Map::Create(isolate, 0), isolate); if (FLAG_track_constant_fields) { TestReconfigureDataFieldAttribute_GeneralizeField( {PropertyConstness::kConst, Representation::Smi(), any_type}, {PropertyConstness::kConst, Representation::HeapObject(), value_type}, {PropertyConstness::kConst, Representation::Tagged(), any_type}); TestReconfigureDataFieldAttribute_GeneralizeField( {PropertyConstness::kConst, Representation::Smi(), any_type}, {PropertyConstness::kMutable, Representation::HeapObject(), value_type}, {PropertyConstness::kMutable, Representation::Tagged(), any_type}); TestReconfigureDataFieldAttribute_GeneralizeField( {PropertyConstness::kMutable, Representation::Smi(), any_type}, {PropertyConstness::kConst, Representation::HeapObject(), value_type}, {PropertyConstness::kMutable, Representation::Tagged(), any_type}); } TestReconfigureDataFieldAttribute_GeneralizeField( {PropertyConstness::kMutable, Representation::Smi(), any_type}, {PropertyConstness::kMutable, Representation::HeapObject(), value_type}, {PropertyConstness::kMutable, Representation::Tagged(), any_type}); } TEST(ReconfigureDataFieldAttribute_GeneralizeDoubleFieldToTagged) { CcTest::InitializeVM(); v8::HandleScope scope(CcTest::isolate()); Isolate* isolate = CcTest::i_isolate(); Handle any_type = FieldType::Any(isolate); Handle value_type = FieldType::Class(Map::Create(isolate, 0), isolate); if (FLAG_track_constant_fields) { TestReconfigureDataFieldAttribute_GeneralizeField( {PropertyConstness::kConst, Representation::Double(), any_type}, {PropertyConstness::kConst, Representation::HeapObject(), value_type}, {PropertyConstness::kConst, Representation::Tagged(), any_type}); TestReconfigureDataFieldAttribute_GeneralizeField( {PropertyConstness::kConst, Representation::Double(), any_type}, {PropertyConstness::kMutable, Representation::HeapObject(), value_type}, {PropertyConstness::kMutable, Representation::Tagged(), any_type}); TestReconfigureDataFieldAttribute_GeneralizeField( {PropertyConstness::kMutable, Representation::Double(), any_type}, {PropertyConstness::kConst, Representation::HeapObject(), value_type}, {PropertyConstness::kMutable, Representation::Tagged(), any_type}); } TestReconfigureDataFieldAttribute_GeneralizeField( {PropertyConstness::kMutable, Representation::Double(), any_type}, {PropertyConstness::kMutable, Representation::HeapObject(), value_type}, {PropertyConstness::kMutable, Representation::Tagged(), any_type}); } TEST(ReconfigureDataFieldAttribute_GeneralizeHeapObjFieldToHeapObj) { CcTest::InitializeVM(); v8::HandleScope scope(CcTest::isolate()); Isolate* isolate = CcTest::i_isolate(); Handle any_type = FieldType::Any(isolate); Handle current_type = FieldType::Class(Map::Create(isolate, 0), isolate); Handle new_type = FieldType::Class(Map::Create(isolate, 0), isolate); Handle expected_type = any_type; // Check generalizations that trigger deopts. if (FLAG_track_constant_fields) { TestReconfigureDataFieldAttribute_GeneralizeFieldTrivial( {PropertyConstness::kConst, Representation::HeapObject(), current_type}, {PropertyConstness::kConst, Representation::HeapObject(), new_type}, {PropertyConstness::kConst, Representation::HeapObject(), expected_type}); if (FLAG_modify_map_inplace) { // PropertyConstness::kConst to PropertyConstness::kMutable migration does // not create a new map, therefore trivial generalization. TestReconfigureDataFieldAttribute_GeneralizeFieldTrivial( {PropertyConstness::kConst, Representation::HeapObject(), current_type}, {PropertyConstness::kMutable, Representation::HeapObject(), new_type}, {PropertyConstness::kMutable, Representation::HeapObject(), expected_type}); } else { // PropertyConstness::kConst to PropertyConstness::kMutable migration // causes map change, therefore non-trivial generalization. TestReconfigureDataFieldAttribute_GeneralizeField( {PropertyConstness::kConst, Representation::HeapObject(), current_type}, {PropertyConstness::kMutable, Representation::HeapObject(), new_type}, {PropertyConstness::kMutable, Representation::HeapObject(), expected_type}); } TestReconfigureDataFieldAttribute_GeneralizeFieldTrivial( {PropertyConstness::kMutable, Representation::HeapObject(), current_type}, {PropertyConstness::kConst, Representation::HeapObject(), new_type}, {PropertyConstness::kMutable, Representation::HeapObject(), expected_type}); } TestReconfigureDataFieldAttribute_GeneralizeFieldTrivial( {PropertyConstness::kMutable, Representation::HeapObject(), current_type}, {PropertyConstness::kMutable, Representation::HeapObject(), new_type}, {PropertyConstness::kMutable, Representation::HeapObject(), expected_type}); current_type = expected_type; // Check generalizations that do not trigger deopts. new_type = FieldType::Class(Map::Create(isolate, 0), isolate); if (FLAG_track_constant_fields) { TestReconfigureDataFieldAttribute_GeneralizeFieldTrivial( {PropertyConstness::kConst, Representation::HeapObject(), any_type}, {PropertyConstness::kConst, Representation::HeapObject(), new_type}, {PropertyConstness::kConst, Representation::HeapObject(), any_type}, false); if (FLAG_modify_map_inplace) { // PropertyConstness::kConst to PropertyConstness::kMutable migration does // not create a new map, therefore trivial generalization. TestReconfigureDataFieldAttribute_GeneralizeFieldTrivial( {PropertyConstness::kConst, Representation::HeapObject(), any_type}, {PropertyConstness::kMutable, Representation::HeapObject(), new_type}, {PropertyConstness::kMutable, Representation::HeapObject(), any_type}); } else { // PropertyConstness::kConst to PropertyConstness::kMutable migration // causes map change, therefore non-trivial generalization. TestReconfigureDataFieldAttribute_GeneralizeField( {PropertyConstness::kConst, Representation::HeapObject(), any_type}, {PropertyConstness::kMutable, Representation::HeapObject(), new_type}, {PropertyConstness::kMutable, Representation::HeapObject(), any_type}); } TestReconfigureDataFieldAttribute_GeneralizeFieldTrivial( {PropertyConstness::kMutable, Representation::HeapObject(), any_type}, {PropertyConstness::kConst, Representation::HeapObject(), new_type}, {PropertyConstness::kMutable, Representation::HeapObject(), any_type}, false); } TestReconfigureDataFieldAttribute_GeneralizeFieldTrivial( {PropertyConstness::kMutable, Representation::HeapObject(), any_type}, {PropertyConstness::kMutable, Representation::HeapObject(), new_type}, {PropertyConstness::kMutable, Representation::HeapObject(), any_type}, false); } TEST(ReconfigureDataFieldAttribute_GeneralizeHeapObjectFieldToTagged) { CcTest::InitializeVM(); v8::HandleScope scope(CcTest::isolate()); Isolate* isolate = CcTest::i_isolate(); Handle any_type = FieldType::Any(isolate); Handle value_type = FieldType::Class(Map::Create(isolate, 0), isolate); TestReconfigureDataFieldAttribute_GeneralizeField( {PropertyConstness::kMutable, Representation::HeapObject(), value_type}, {PropertyConstness::kMutable, Representation::Smi(), any_type}, {PropertyConstness::kMutable, 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(Isolate* isolate, Handle map, Handle new_map, const Expectations& expectations) { CHECK(map->is_deprecated()); CHECK_NE(*map, *new_map); CHECK(!new_map->is_deprecated()); CHECK(expectations.Check(*new_map)); // Update deprecated |map|, it should become |new_map|. Handle updated_map = Map::Update(isolate, map); CHECK_EQ(*new_map, *updated_map); } }; // Checks that given |map| is NOT deprecated, equals to given |new_map| and // matches expectations. struct CheckSameMap { void Check(Isolate* isolate, Handle map, Handle new_map, const Expectations& expectations) { // |map| was not reconfigured, therefore it should stay stable. CHECK(map->is_stable()); CHECK(!map->is_deprecated()); CHECK_EQ(*map, *new_map); CHECK(!new_map->is_deprecated()); CHECK(expectations.Check(*new_map)); // Update deprecated |map|, it should become |new_map|. Handle updated_map = Map::Update(isolate, map); CHECK_EQ(*new_map, *updated_map); } }; // Checks that given |map| is NOT deprecated and matches expectations. // |new_map| is unrelated to |map|. struct CheckUnrelated { void Check(Isolate* isolate, Handle map, Handle new_map, const Expectations& expectations) { CHECK(!map->is_deprecated()); CHECK_NE(*map, *new_map); CHECK(expectations.Check(*map)); CHECK(new_map->is_stable()); CHECK(!new_map->is_deprecated()); } }; // Checks that given |map| is NOT deprecated, and |new_map| is a result of // copy-generalize-all-representations. struct CheckCopyGeneralizeAllFields { void Check(Isolate* isolate, Handle map, Handle new_map, Expectations& expectations) { CHECK(!map->is_deprecated()); CHECK_NE(*map, *new_map); CHECK(new_map->GetBackPointer()->IsUndefined(isolate)); for (int i = 0; i < kPropCount; i++) { expectations.GeneralizeField(i); } CHECK(!new_map->is_deprecated()); CHECK(expectations.Check(*new_map)); } }; // This test ensures that field 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 = FieldType::Any(isolate); const int kCustomPropIndex = kPropCount - 2; Expectations expectations(isolate); const int kSplitProp = 2; CHECK_LT(kSplitProp, kCustomPropIndex); const PropertyConstness constness = PropertyConstness::kMutable; 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, constness, 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, constness, representation, any_type); } map1 = config.AddPropertyAtBranch(1, expectations1, map1); for (int i = kCustomPropIndex + 1; i < kPropCount; i++) { map1 = expectations1.AddDataField(map1, NONE, constness, 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, constness, representation, any_type); for (int i = kSplitProp + 1; i < kCustomPropIndex; i++) { map2 = expectations2.AddDataField(map2, NONE, constness, representation, any_type); } map2 = config.AddPropertyAtBranch(2, expectations2, map2); for (int i = kCustomPropIndex + 1; i < kPropCount; i++) { map2 = expectations2.AddDataField(map2, NONE, constness, 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(isolate, map2, kSplitProp, kData, NONE); // |map2| should be left unchanged but marked unstable. CHECK(!map2->is_stable()); CHECK(!map2->is_deprecated()); CHECK_NE(*map2, *new_map); CHECK(expectations2.Check(*map2)); config.UpdateExpectations(kCustomPropIndex, expectations1); checker.Check(isolate, 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->NewFunctionForTest(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_; Handle function_type_; TestConfig() { Isolate* isolate = CcTest::i_isolate(); Factory* factory = isolate->factory(); Handle name = factory->empty_string(); Handle sloppy_map = Map::CopyInitialMap(isolate, isolate->sloppy_function_map()); Handle info = factory->NewSharedFunctionInfoForBuiltin(name, Builtins::kIllegal); function_type_ = FieldType::Class(sloppy_map, isolate); CHECK(sloppy_map->is_stable()); js_func1_ = factory->NewFunction(sloppy_map, info, isolate->native_context()); js_func2_ = factory->NewFunction(sloppy_map, info, isolate->native_context()); } 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) { PropertyConstness expected_constness = FLAG_track_constant_fields ? PropertyConstness::kConst : PropertyConstness::kMutable; expectations.SetDataField(property_index, expected_constness, Representation::HeapObject(), function_type_); } }; TestConfig config; if (FLAG_track_constant_fields) { CheckSameMap checker; TestReconfigureProperty_CustomPropertyAfterTargetMap(config, checker); } else { // Two branches are "incompatible" so the |map1| should be deprecated. CheckDeprecated checker; TestReconfigureProperty_CustomPropertyAfterTargetMap(config, checker); } } TEST(ReconfigureDataFieldAttribute_DataConstantToAccConstantAfterTargetMap) { CcTest::InitializeVM(); v8::HandleScope scope(CcTest::isolate()); struct TestConfig { Handle js_func_; Handle pair_; TestConfig() { Isolate* isolate = CcTest::i_isolate(); Factory* factory = isolate->factory(); js_func_ = factory->NewFunctionForTest(factory->empty_string()); pair_ = CreateAccessorPair(true, true); } Handle AddPropertyAtBranch(int branch_id, Expectations& expectations, Handle map) { CHECK(branch_id == 1 || branch_id == 2); if (branch_id == 1) { return expectations.AddDataConstant(map, NONE, js_func_); } else { return expectations.AddAccessorConstant(map, NONE, pair_); } } void UpdateExpectations(int property_index, Expectations& expectations) {} }; TestConfig config; // These are completely separate branches in transition tree. CheckUnrelated 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_); } void UpdateExpectations(int property_index, Expectations& expectations) { // Two branches are "compatible" so the |map1| should NOT be deprecated. } }; 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) { CheckCopyGeneralizeAllFields checker; TestReconfigureProperty_CustomPropertyAfterTargetMap(config, checker); } else { // Currently we have a copy-generalize-all-representations case. CheckCopyGeneralizeAllFields checker; TestReconfigureProperty_CustomPropertyAfterTargetMap(config, checker); } } TEST(ReconfigureDataFieldAttribute_AccConstantToDataFieldAfterTargetMap) { 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); if (branch_id == 1) { return expectations.AddAccessorConstant(map, NONE, pair_); } else { Isolate* isolate = CcTest::i_isolate(); Handle any_type = FieldType::Any(isolate); return expectations.AddDataField(map, NONE, kDefaultFieldConstness, Representation::Smi(), any_type); } } void UpdateExpectations(int property_index, Expectations& expectations) {} }; TestConfig config; // These are completely separate branches in transition tree. CheckUnrelated checker; TestReconfigureProperty_CustomPropertyAfterTargetMap(config, checker); } //////////////////////////////////////////////////////////////////////////////// // A set of tests for elements kind reconfiguration case. // // This test ensures that field generalization is correctly propagated from one // branch of transition tree (|map2) to another (|map|). // // + - p0 - p1 - p2A - p3 - p4: |map| // | // ek // | // {} - p0 - p1 - p2B - p3 - p4: |map2| // // where "p2A" and "p2B" differ only in the representation/field type. // static void TestReconfigureElementsKind_GeneralizeField( const CRFTData& from, const CRFTData& to, const CRFTData& expected) { Isolate* isolate = CcTest::i_isolate(); Expectations expectations(isolate, PACKED_SMI_ELEMENTS); // Create a map, add required properties to it and initialize expectations. Handle initial_map = Map::Create(isolate, 0); initial_map->set_instance_type(JS_ARRAY_TYPE); initial_map->set_elements_kind(PACKED_SMI_ELEMENTS); Handle map = initial_map; map = expectations.AsElementsKind(map, PACKED_ELEMENTS); for (int i = 0; i < kPropCount; i++) { map = expectations.AddDataField(map, NONE, from.constness, 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 |kDiffProp| // has different representatio/field type), initialize expectations. const int kDiffProp = kPropCount / 2; Expectations expectations2(isolate, PACKED_SMI_ELEMENTS); Handle map2 = initial_map; for (int i = 0; i < kPropCount; i++) { if (i == kDiffProp) { map2 = expectations2.AddDataField(map2, NONE, to.constness, to.representation, to.type); } else { map2 = expectations2.AddDataField(map2, NONE, from.constness, from.representation, from.type); } } CHECK(!map2->is_deprecated()); CHECK(map2->is_stable()); CHECK(expectations2.Check(*map2)); Zone zone(isolate->allocator(), ZONE_NAME); CanonicalHandleScope canonical(isolate); JSHeapBroker broker(isolate, &zone); CompilationDependencies dependencies(isolate, &zone); MapRef map_ref(&broker, map); map_ref.SerializeOwnDescriptors(); dependencies.DependOnFieldType(map_ref, kDiffProp); // Reconfigure elements kinds of |map2|, which should generalize // representations in |map|. Handle new_map = Map::ReconfigureElementsKind(isolate, map2, PACKED_ELEMENTS); // |map2| should be left unchanged but marked unstable. CHECK(!map2->is_stable()); CHECK(!map2->is_deprecated()); CHECK_NE(*map2, *new_map); CHECK(expectations2.Check(*map2)); // |map| should be deprecated and |new_map| should match new expectations. expectations.SetDataField(kDiffProp, expected.constness, expected.representation, expected.type); CHECK(map->is_deprecated()); CHECK(dependencies.AreValid()); CHECK_NE(*map, *new_map); CHECK(!new_map->is_deprecated()); CHECK(expectations.Check(*new_map)); // Update deprecated |map|, it should become |new_map|. Handle updated_map = Map::Update(isolate, map); CHECK_EQ(*new_map, *updated_map); // Ensure Map::FindElementsKindTransitionedMap() is able to find the // transitioned map. { MapHandles map_list; map_list.push_back(updated_map); Map* transitioned_map = map2->FindElementsKindTransitionedMap(isolate, map_list); CHECK_EQ(*updated_map, transitioned_map); } } // This test ensures that trivial field generalization (from HeapObject to // HeapObject) is correctly propagated from one branch of transition tree // (|map2|) to another (|map|). // // + - p0 - p1 - p2A - p3 - p4: |map| // | // ek // | // {} - p0 - p1 - p2B - p3 - p4: |map2| // // where "p2A" and "p2B" differ only in the representation/field type. // static void TestReconfigureElementsKind_GeneralizeFieldTrivial( const CRFTData& from, const CRFTData& to, const CRFTData& expected) { Isolate* isolate = CcTest::i_isolate(); Expectations expectations(isolate, PACKED_SMI_ELEMENTS); // Create a map, add required properties to it and initialize expectations. Handle initial_map = Map::Create(isolate, 0); initial_map->set_instance_type(JS_ARRAY_TYPE); initial_map->set_elements_kind(PACKED_SMI_ELEMENTS); Handle map = initial_map; map = expectations.AsElementsKind(map, PACKED_ELEMENTS); for (int i = 0; i < kPropCount; i++) { map = expectations.AddDataField(map, NONE, from.constness, 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 |kDiffProp| // has different attributes), initialize expectations. const int kDiffProp = kPropCount / 2; Expectations expectations2(isolate, PACKED_SMI_ELEMENTS); Handle map2 = initial_map; for (int i = 0; i < kPropCount; i++) { if (i == kDiffProp) { map2 = expectations2.AddDataField(map2, NONE, to.constness, to.representation, to.type); } else { map2 = expectations2.AddDataField(map2, NONE, from.constness, from.representation, from.type); } } CHECK(!map2->is_deprecated()); CHECK(map2->is_stable()); CHECK(expectations2.Check(*map2)); Zone zone(isolate->allocator(), ZONE_NAME); CanonicalHandleScope canonical(isolate); JSHeapBroker broker(isolate, &zone); CompilationDependencies dependencies(isolate, &zone); MapRef map_ref(&broker, map); map_ref.SerializeOwnDescriptors(); dependencies.DependOnFieldType(map_ref, kDiffProp); // Reconfigure elements kinds of |map2|, which should generalize // representations in |map|. Handle new_map = Map::ReconfigureElementsKind(isolate, map2, PACKED_ELEMENTS); // |map2| should be left unchanged but marked unstable. CHECK(!map2->is_stable()); 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 elements // kind 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. expectations.SetDataField(kDiffProp, expected.constness, expected.representation, expected.type); CHECK(!map->is_deprecated()); CHECK_EQ(*map, *new_map); CHECK(dependencies.AreValid()); CHECK(!new_map->is_deprecated()); CHECK(expectations.Check(*new_map)); Handle updated_map = Map::Update(isolate, map); CHECK_EQ(*new_map, *updated_map); // Ensure Map::FindElementsKindTransitionedMap() is able to find the // transitioned map. { MapHandles map_list; map_list.push_back(updated_map); Map* transitioned_map = map2->FindElementsKindTransitionedMap(isolate, map_list); CHECK_EQ(*updated_map, transitioned_map); } } TEST(ReconfigureElementsKind_GeneralizeSmiFieldToDouble) { CcTest::InitializeVM(); v8::HandleScope scope(CcTest::isolate()); Isolate* isolate = CcTest::i_isolate(); Handle any_type = FieldType::Any(isolate); if (FLAG_track_constant_fields) { TestReconfigureElementsKind_GeneralizeField( {PropertyConstness::kConst, Representation::Smi(), any_type}, {PropertyConstness::kConst, Representation::Double(), any_type}, {PropertyConstness::kConst, Representation::Double(), any_type}); TestReconfigureElementsKind_GeneralizeField( {PropertyConstness::kConst, Representation::Smi(), any_type}, {PropertyConstness::kMutable, Representation::Double(), any_type}, {PropertyConstness::kMutable, Representation::Double(), any_type}); TestReconfigureElementsKind_GeneralizeField( {PropertyConstness::kMutable, Representation::Smi(), any_type}, {PropertyConstness::kConst, Representation::Double(), any_type}, {PropertyConstness::kMutable, Representation::Double(), any_type}); } TestReconfigureElementsKind_GeneralizeField( {PropertyConstness::kMutable, Representation::Smi(), any_type}, {PropertyConstness::kMutable, Representation::Double(), any_type}, {PropertyConstness::kMutable, Representation::Double(), any_type}); } TEST(ReconfigureElementsKind_GeneralizeSmiFieldToTagged) { CcTest::InitializeVM(); v8::HandleScope scope(CcTest::isolate()); Isolate* isolate = CcTest::i_isolate(); Handle any_type = FieldType::Any(isolate); Handle value_type = FieldType::Class(Map::Create(isolate, 0), isolate); if (FLAG_track_constant_fields) { TestReconfigureElementsKind_GeneralizeField( {PropertyConstness::kConst, Representation::Smi(), any_type}, {PropertyConstness::kConst, Representation::HeapObject(), value_type}, {PropertyConstness::kConst, Representation::Tagged(), any_type}); TestReconfigureElementsKind_GeneralizeField( {PropertyConstness::kConst, Representation::Smi(), any_type}, {PropertyConstness::kMutable, Representation::HeapObject(), value_type}, {PropertyConstness::kMutable, Representation::Tagged(), any_type}); TestReconfigureElementsKind_GeneralizeField( {PropertyConstness::kMutable, Representation::Smi(), any_type}, {PropertyConstness::kConst, Representation::HeapObject(), value_type}, {PropertyConstness::kMutable, Representation::Tagged(), any_type}); } TestReconfigureElementsKind_GeneralizeField( {PropertyConstness::kMutable, Representation::Smi(), any_type}, {PropertyConstness::kMutable, Representation::HeapObject(), value_type}, {PropertyConstness::kMutable, Representation::Tagged(), any_type}); } TEST(ReconfigureElementsKind_GeneralizeDoubleFieldToTagged) { CcTest::InitializeVM(); v8::HandleScope scope(CcTest::isolate()); Isolate* isolate = CcTest::i_isolate(); Handle any_type = FieldType::Any(isolate); Handle value_type = FieldType::Class(Map::Create(isolate, 0), isolate); if (FLAG_track_constant_fields) { TestReconfigureElementsKind_GeneralizeField( {PropertyConstness::kConst, Representation::Double(), any_type}, {PropertyConstness::kConst, Representation::HeapObject(), value_type}, {PropertyConstness::kConst, Representation::Tagged(), any_type}); TestReconfigureElementsKind_GeneralizeField( {PropertyConstness::kConst, Representation::Double(), any_type}, {PropertyConstness::kMutable, Representation::HeapObject(), value_type}, {PropertyConstness::kMutable, Representation::Tagged(), any_type}); TestReconfigureElementsKind_GeneralizeField( {PropertyConstness::kMutable, Representation::Double(), any_type}, {PropertyConstness::kConst, Representation::HeapObject(), value_type}, {PropertyConstness::kMutable, Representation::Tagged(), any_type}); } TestReconfigureElementsKind_GeneralizeField( {PropertyConstness::kMutable, Representation::Double(), any_type}, {PropertyConstness::kMutable, Representation::HeapObject(), value_type}, {PropertyConstness::kMutable, Representation::Tagged(), any_type}); } TEST(ReconfigureElementsKind_GeneralizeHeapObjFieldToHeapObj) { CcTest::InitializeVM(); v8::HandleScope scope(CcTest::isolate()); Isolate* isolate = CcTest::i_isolate(); Handle any_type = FieldType::Any(isolate); Handle current_type = FieldType::Class(Map::Create(isolate, 0), isolate); Handle new_type = FieldType::Class(Map::Create(isolate, 0), isolate); Handle expected_type = any_type; // Check generalizations that trigger deopts. if (FLAG_track_constant_fields) { TestReconfigureElementsKind_GeneralizeFieldTrivial( {PropertyConstness::kConst, Representation::HeapObject(), current_type}, {PropertyConstness::kConst, Representation::HeapObject(), new_type}, {PropertyConstness::kConst, Representation::HeapObject(), expected_type}); if (FLAG_modify_map_inplace) { // PropertyConstness::kConst to PropertyConstness::kMutable migration does // not create a new map, therefore trivial generalization. TestReconfigureElementsKind_GeneralizeFieldTrivial( {PropertyConstness::kConst, Representation::HeapObject(), current_type}, {PropertyConstness::kMutable, Representation::HeapObject(), new_type}, {PropertyConstness::kMutable, Representation::HeapObject(), expected_type}); } else { // PropertyConstness::kConst to PropertyConstness::kMutable migration // causes map change, therefore non-trivial generalization. TestReconfigureElementsKind_GeneralizeField( {PropertyConstness::kConst, Representation::HeapObject(), current_type}, {PropertyConstness::kMutable, Representation::HeapObject(), new_type}, {PropertyConstness::kMutable, Representation::HeapObject(), expected_type}); } TestReconfigureElementsKind_GeneralizeFieldTrivial( {PropertyConstness::kMutable, Representation::HeapObject(), current_type}, {PropertyConstness::kConst, Representation::HeapObject(), new_type}, {PropertyConstness::kMutable, Representation::HeapObject(), expected_type}); } TestReconfigureElementsKind_GeneralizeFieldTrivial( {PropertyConstness::kMutable, Representation::HeapObject(), current_type}, {PropertyConstness::kMutable, Representation::HeapObject(), new_type}, {PropertyConstness::kMutable, Representation::HeapObject(), expected_type}); current_type = expected_type; // Check generalizations that do not trigger deopts. new_type = FieldType::Class(Map::Create(isolate, 0), isolate); if (FLAG_track_constant_fields) { TestReconfigureElementsKind_GeneralizeFieldTrivial( {PropertyConstness::kConst, Representation::HeapObject(), any_type}, {PropertyConstness::kConst, Representation::HeapObject(), new_type}, {PropertyConstness::kConst, Representation::HeapObject(), any_type}); if (FLAG_modify_map_inplace) { // PropertyConstness::kConst to PropertyConstness::kMutable migration does // not create a new map, therefore trivial generalization. TestReconfigureElementsKind_GeneralizeFieldTrivial( {PropertyConstness::kConst, Representation::HeapObject(), any_type}, {PropertyConstness::kMutable, Representation::HeapObject(), new_type}, {PropertyConstness::kMutable, Representation::HeapObject(), any_type}); } else { // PropertyConstness::kConst to PropertyConstness::kMutable migration // causes map change, therefore non-trivial generalization. TestReconfigureElementsKind_GeneralizeField( {PropertyConstness::kConst, Representation::HeapObject(), any_type}, {PropertyConstness::kMutable, Representation::HeapObject(), new_type}, {PropertyConstness::kMutable, Representation::HeapObject(), any_type}); } TestReconfigureElementsKind_GeneralizeFieldTrivial( {PropertyConstness::kMutable, Representation::HeapObject(), any_type}, {PropertyConstness::kConst, Representation::HeapObject(), new_type}, {PropertyConstness::kMutable, Representation::HeapObject(), any_type}); } TestReconfigureElementsKind_GeneralizeFieldTrivial( {PropertyConstness::kMutable, Representation::HeapObject(), any_type}, {PropertyConstness::kMutable, Representation::HeapObject(), new_type}, {PropertyConstness::kMutable, Representation::HeapObject(), any_type}); } TEST(ReconfigureElementsKind_GeneralizeHeapObjectFieldToTagged) { CcTest::InitializeVM(); v8::HandleScope scope(CcTest::isolate()); Isolate* isolate = CcTest::i_isolate(); Handle any_type = FieldType::Any(isolate); Handle value_type = FieldType::Class(Map::Create(isolate, 0), isolate); if (FLAG_track_constant_fields) { TestReconfigureElementsKind_GeneralizeField( {PropertyConstness::kConst, Representation::HeapObject(), value_type}, {PropertyConstness::kConst, Representation::Smi(), any_type}, {PropertyConstness::kConst, Representation::Tagged(), any_type}); TestReconfigureElementsKind_GeneralizeField( {PropertyConstness::kConst, Representation::HeapObject(), value_type}, {PropertyConstness::kMutable, Representation::Smi(), any_type}, {PropertyConstness::kMutable, Representation::Tagged(), any_type}); TestReconfigureElementsKind_GeneralizeField( {PropertyConstness::kMutable, Representation::HeapObject(), value_type}, {PropertyConstness::kConst, Representation::Smi(), any_type}, {PropertyConstness::kMutable, Representation::Tagged(), any_type}); } TestReconfigureElementsKind_GeneralizeField( {PropertyConstness::kMutable, Representation::HeapObject(), value_type}, {PropertyConstness::kMutable, Representation::Smi(), any_type}, {PropertyConstness::kMutable, Representation::Tagged(), any_type}); } //////////////////////////////////////////////////////////////////////////////// // 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 = FieldType::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, PropertyConstness::kMutable, 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); Map* target = TransitionsAccessor(isolate, map2) .SearchTransition(*name, kData, NONE); CHECK_NOT_NULL(target); map2 = handle(target, isolate); } map2 = Map::ReconfigureProperty(isolate, map2, kSplitProp, kData, NONE, Representation::Double(), any_type); expectations.SetDataField(kSplitProp, PropertyConstness::kMutable, 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_stable()); CHECK(!map2->is_deprecated()); // Fill in transition tree of |map2| so that it can't have more transitions. for (int i = 0; i < TransitionsAccessor::kMaxNumberOfTransitions; i++) { CHECK(TransitionsAccessor(isolate, map2).CanHaveMoreTransitions()); Handle name = MakeName("foo", i); Map::CopyWithField(isolate, map2, name, any_type, NONE, PropertyConstness::kMutable, Representation::Smi(), INSERT_TRANSITION) .ToHandleChecked(); } CHECK(!TransitionsAccessor(isolate, 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(isolate, map); CHECK(updated_map->GetBackPointer()->IsUndefined(isolate)); for (int i = 0; i < kPropCount; i++) { expectations.SetDataField(i, PropertyConstness::kMutable, 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 field 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_GeneralizeField once // IS_PROTO_TRANS_ISSUE_FIXED and IS_NON_EQUIVALENT_TRANSITION_SUPPORTED are // fixed. template static void TestGeneralizeFieldWithSpecialTransition(TestConfig& config, const CRFTData& from, const CRFTData& to, const CRFTData& expected) { 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.constness, from.representation, from.type); } CHECK(!map->is_deprecated()); CHECK(map->is_stable()); CHECK(expectations.Check(*map)); Expectations expectations2 = expectations; // Apply some special transition to |map|. CHECK(map->owns_descriptors()); Handle map2 = config.Transition(map, expectations2); // |map| should still match expectations. CHECK(!map->is_deprecated()); CHECK(expectations.Check(*map)); if (config.generalizes_representations()) { for (int i = 0; i < kPropCount; i++) { expectations2.GeneralizeField(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(isolate, map, i, kData, NONE, to.representation, to.type); maps[i] = new_map; expectations.SetDataField(i, expected.constness, 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(isolate, map2); CHECK(!new_map2->is_deprecated()); CHECK(!new_map2->is_dictionary_map()); Handle tmp_map; if (Map::TryUpdate(isolate, map2).ToHandle(&tmp_map)) { // If Map::TryUpdate() manages to succeed the result must match the result // of Map::Update(). CHECK_EQ(*new_map2, *tmp_map); } if (config.is_non_equevalent_transition()) { // In case of non-equivalent transition currently we generalize all // representations. for (int i = 0; i < kPropCount; i++) { expectations2.GeneralizeField(i); } CHECK(new_map2->GetBackPointer()->IsUndefined(isolate)); CHECK(expectations2.Check(*new_map2)); } else { CHECK(!new_map2->GetBackPointer()->IsUndefined(isolate)); CHECK(expectations2.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. Handle updated_map = Map::Update(isolate, map); CHECK_EQ(*active_map, *updated_map); for (int i = 0; i < kPropCount; i++) { updated_map = Map::Update(isolate, maps[i]); CHECK_EQ(*active_map, *updated_map); } } TEST(ElementsKindTransitionFromMapOwningDescriptor) { CcTest::InitializeVM(); v8::HandleScope scope(CcTest::isolate()); Isolate* isolate = CcTest::i_isolate(); Handle any_type = FieldType::Any(isolate); Handle value_type = FieldType::Class(Map::Create(isolate, 0), isolate); struct TestConfig { Handle Transition(Handle map, Expectations& expectations) { Handle frozen_symbol(map->GetReadOnlyRoots().frozen_symbol(), CcTest::i_isolate()); expectations.SetElementsKind(DICTIONARY_ELEMENTS); return Map::CopyForPreventExtensions(CcTest::i_isolate(), map, NONE, frozen_symbol, "CopyForPreventExtensions"); } // TODO(ishell): remove once IS_PROTO_TRANS_ISSUE_FIXED is removed. bool generalizes_representations() const { return false; } bool is_non_equevalent_transition() const { return true; } }; TestConfig config; TestGeneralizeFieldWithSpecialTransition( config, {PropertyConstness::kMutable, Representation::Smi(), any_type}, {PropertyConstness::kMutable, Representation::HeapObject(), value_type}, {PropertyConstness::kMutable, Representation::Tagged(), any_type}); } TEST(ElementsKindTransitionFromMapNotOwningDescriptor) { CcTest::InitializeVM(); v8::HandleScope scope(CcTest::isolate()); Isolate* isolate = CcTest::i_isolate(); Handle any_type = FieldType::Any(isolate); Handle value_type = FieldType::Class(Map::Create(isolate, 0), isolate); struct TestConfig { Handle Transition(Handle map, Expectations& expectations) { Isolate* isolate = CcTest::i_isolate(); Handle any_type = FieldType::Any(isolate); // Add one more transition to |map| in order to prevent descriptors // ownership. CHECK(map->owns_descriptors()); Map::CopyWithField(isolate, map, MakeString("foo"), any_type, NONE, PropertyConstness::kMutable, Representation::Smi(), INSERT_TRANSITION) .ToHandleChecked(); CHECK(!map->owns_descriptors()); Handle frozen_symbol(ReadOnlyRoots(isolate).frozen_symbol(), isolate); expectations.SetElementsKind(DICTIONARY_ELEMENTS); return Map::CopyForPreventExtensions(isolate, map, NONE, frozen_symbol, "CopyForPreventExtensions"); } // TODO(ishell): remove once IS_PROTO_TRANS_ISSUE_FIXED is removed. bool generalizes_representations() const { return false; } bool is_non_equevalent_transition() const { return true; } }; TestConfig config; TestGeneralizeFieldWithSpecialTransition( config, {PropertyConstness::kMutable, Representation::Smi(), any_type}, {PropertyConstness::kMutable, Representation::HeapObject(), value_type}, {PropertyConstness::kMutable, Representation::Tagged(), any_type}); } TEST(PrototypeTransitionFromMapOwningDescriptor) { CcTest::InitializeVM(); v8::HandleScope scope(CcTest::isolate()); Isolate* isolate = CcTest::i_isolate(); Handle any_type = FieldType::Any(isolate); Handle value_type = FieldType::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, Expectations& expectations) { return Map::TransitionToPrototype(CcTest::i_isolate(), map, prototype_); } // TODO(ishell): remove once IS_PROTO_TRANS_ISSUE_FIXED is removed. bool generalizes_representations() const { return !IS_PROTO_TRANS_ISSUE_FIXED; } bool is_non_equevalent_transition() const { return true; } }; TestConfig config; TestGeneralizeFieldWithSpecialTransition( config, {PropertyConstness::kMutable, Representation::Smi(), any_type}, {PropertyConstness::kMutable, Representation::HeapObject(), value_type}, {PropertyConstness::kMutable, Representation::Tagged(), any_type}); } TEST(PrototypeTransitionFromMapNotOwningDescriptor) { CcTest::InitializeVM(); v8::HandleScope scope(CcTest::isolate()); Isolate* isolate = CcTest::i_isolate(); Handle any_type = FieldType::Any(isolate); Handle value_type = FieldType::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, Expectations& expectations) { Isolate* isolate = CcTest::i_isolate(); Handle any_type = FieldType::Any(isolate); // Add one more transition to |map| in order to prevent descriptors // ownership. CHECK(map->owns_descriptors()); Map::CopyWithField(isolate, map, MakeString("foo"), any_type, NONE, PropertyConstness::kMutable, Representation::Smi(), INSERT_TRANSITION) .ToHandleChecked(); CHECK(!map->owns_descriptors()); return Map::TransitionToPrototype(isolate, map, prototype_); } // TODO(ishell): remove once IS_PROTO_TRANS_ISSUE_FIXED is removed. bool generalizes_representations() const { return !IS_PROTO_TRANS_ISSUE_FIXED; } bool is_non_equevalent_transition() const { return true; } }; TestConfig config; TestGeneralizeFieldWithSpecialTransition( config, {PropertyConstness::kMutable, Representation::Smi(), any_type}, {PropertyConstness::kMutable, Representation::HeapObject(), value_type}, {PropertyConstness::kMutable, Representation::Tagged(), any_type}); } //////////////////////////////////////////////////////////////////////////////// // A set of tests for higher level transitioning mechanics. // struct TransitionToDataFieldOperator { PropertyConstness constness_; Representation representation_; PropertyAttributes attributes_; Handle heap_type_; Handle value_; TransitionToDataFieldOperator(PropertyConstness constness, Representation representation, Handle heap_type, Handle value, PropertyAttributes attributes = NONE) : constness_(constness), representation_(representation), attributes_(attributes), heap_type_(heap_type), value_(value) {} Handle DoTransition(Expectations& expectations, Handle map) { return expectations.TransitionToDataField( map, attributes_, constness_, 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(Isolate* isolate, Expectations& expectations, Handle map) { expectations.SetDataField(descriptor_, PropertyConstness::kMutable, representation_, heap_type_); return Map::ReconfigureExistingProperty(isolate, map, descriptor_, kData, attributes_); } }; struct ReconfigureAsAccessorPropertyOperator { int descriptor_; PropertyAttributes attributes_; ReconfigureAsAccessorPropertyOperator(int descriptor, PropertyAttributes attributes = NONE) : descriptor_(descriptor), attributes_(attributes) {} Handle DoTransition(Isolate* isolate, Expectations& expectations, Handle map) { expectations.SetAccessorField(descriptor_); return Map::ReconfigureExistingProperty(isolate, map, descriptor_, kAccessor, attributes_); } }; // Checks that field generalization happened. struct FieldGeneralizationChecker { int descriptor_; PropertyConstness constness_; Representation representation_; PropertyAttributes attributes_; Handle heap_type_; FieldGeneralizationChecker(int descriptor, PropertyConstness constness, Representation representation, Handle heap_type, PropertyAttributes attributes = NONE) : descriptor_(descriptor), constness_(constness), representation_(representation), attributes_(attributes), heap_type_(heap_type) {} void Check(Isolate* isolate, Expectations& expectations2, Handle map1, Handle map2) { CHECK(!map2->is_deprecated()); CHECK(map1->is_deprecated()); CHECK_NE(*map1, *map2); Handle updated_map = Map::Update(isolate, map1); CHECK_EQ(*map2, *updated_map); expectations2.SetDataField(descriptor_, attributes_, constness_, representation_, heap_type_); CHECK(expectations2.Check(*map2)); } }; // Checks that existing transition was taken as is. struct SameMapChecker { void Check(Isolate* isolate, 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 = FieldType::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, PropertyConstness::kMutable, 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(isolate, expectations2, map1, map2); } TEST(TransitionDataFieldToDataField) { CcTest::InitializeVM(); v8::HandleScope scope(CcTest::isolate()); Isolate* isolate = CcTest::i_isolate(); Handle any_type = FieldType::Any(isolate); Handle value1 = handle(Smi::kZero, isolate); TransitionToDataFieldOperator transition_op1( PropertyConstness::kMutable, Representation::Smi(), any_type, value1); Handle value2 = isolate->factory()->NewHeapNumber(0); TransitionToDataFieldOperator transition_op2( PropertyConstness::kMutable, Representation::Double(), any_type, value2); FieldGeneralizationChecker checker(kPropCount - 1, PropertyConstness::kMutable, 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->NewFunctionForTest(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 name = factory->empty_string(); Handle sloppy_map = Map::CopyInitialMap(isolate, isolate->sloppy_function_map()); Handle info = factory->NewSharedFunctionInfoForBuiltin(name, Builtins::kIllegal); Handle function_type = FieldType::Class(sloppy_map, isolate); CHECK(sloppy_map->is_stable()); Handle js_func1 = factory->NewFunction(sloppy_map, info, isolate->native_context()); TransitionToDataConstantOperator transition_op1(js_func1); Handle js_func2 = factory->NewFunction(sloppy_map, info, isolate->native_context()); TransitionToDataConstantOperator transition_op2(js_func2); if (FLAG_track_constant_fields) { SameMapChecker checker; TestTransitionTo(transition_op1, transition_op2, checker); } else { FieldGeneralizationChecker checker( kPropCount - 1, PropertyConstness::kMutable, Representation::HeapObject(), function_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 = FieldType::Any(isolate); Handle js_func1 = factory->NewFunctionForTest(factory->empty_string()); TransitionToDataConstantOperator transition_op1(js_func1); Handle value2 = isolate->factory()->NewHeapNumber(0); TransitionToDataFieldOperator transition_op2( PropertyConstness::kMutable, Representation::Double(), any_type, value2); FieldGeneralizationChecker checker(kPropCount - 1, PropertyConstness::kMutable, 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) TEST(HoleyMutableHeapNumber) { CcTest::InitializeVM(); v8::HandleScope scope(CcTest::isolate()); Isolate* isolate = CcTest::i_isolate(); auto mhn = isolate->factory()->NewMutableHeapNumberWithHoleNaN(); CHECK_EQ(kHoleNanInt64, mhn->value_as_bits()); mhn = isolate->factory()->NewMutableHeapNumber(0.0); CHECK_EQ(uint64_t{0}, mhn->value_as_bits()); mhn->set_value_as_bits(kHoleNanInt64); CHECK_EQ(kHoleNanInt64, mhn->value_as_bits()); // Ensure that new storage for uninitialized value or mutable heap number // with uninitialized sentinel (kHoleNanInt64) is a mutable heap number // with uninitialized sentinel. Handle obj = Object::NewStorageFor(isolate, isolate->factory()->uninitialized_value(), Representation::Double()); CHECK(obj->IsMutableHeapNumber()); CHECK_EQ(kHoleNanInt64, MutableHeapNumber::cast(*obj)->value_as_bits()); obj = Object::NewStorageFor(isolate, mhn, Representation::Double()); CHECK(obj->IsMutableHeapNumber()); CHECK_EQ(kHoleNanInt64, MutableHeapNumber::cast(*obj)->value_as_bits()); } } // namespace test_field_type_tracking } // namespace compiler } // namespace internal } // namespace v8