| // Copyright 2020 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 "src/base/logging.h" | 
 | #include "src/codegen/code-stub-assembler.h" | 
 | #include "src/common/globals.h" | 
 | #include "src/objects/descriptor-array.h" | 
 | #include "src/objects/property-details.h" | 
 | #include "src/objects/string-inl.h" | 
 | #include "src/objects/transitions-inl.h" | 
 | #include "test/cctest/cctest.h" | 
 | #include "test/cctest/compiler/code-assembler-tester.h" | 
 | #include "test/cctest/compiler/function-tester.h" | 
 | #include "test/cctest/test-transitions.h" | 
 |  | 
 | namespace v8 { | 
 | namespace internal { | 
 |  | 
 | namespace { | 
 |  | 
 | using Label = compiler::CodeAssemblerLabel; | 
 | template <class T> | 
 | using TVariable = compiler::TypedCodeAssemblerVariable<T>; | 
 |  | 
 | Handle<Name> NewNameWithHash(Isolate* isolate, const char* str, uint32_t hash, | 
 |                              bool is_integer) { | 
 |   uint32_t hash_field = hash << Name::kHashShift; | 
 |  | 
 |   static_assert(Name::kNofHashBitFields == 2, "This test needs updating"); | 
 |   static_assert(Name::kHashNotComputedMask == 1, "This test needs updating"); | 
 |   static_assert(Name::kIsNotIntegerIndexMask == 2, "This test needs updating"); | 
 |  | 
 |   if (!is_integer) { | 
 |     hash_field |= Name::kIsNotIntegerIndexMask; | 
 |   } | 
 |   Handle<Name> name = isolate->factory()->NewOneByteInternalizedString( | 
 |       OneByteVector(str), hash_field); | 
 |   name->set_hash_field(hash_field); | 
 |   CHECK(name->IsUniqueName()); | 
 |   return name; | 
 | } | 
 |  | 
 | template <typename... Args> | 
 | MaybeHandle<Object> Call(Isolate* isolate, Handle<JSFunction> function, | 
 |                          Args... args) { | 
 |   const int nof_args = sizeof...(Args); | 
 |   Handle<Object> call_args[] = {args...}; | 
 |   Handle<Object> receiver = isolate->factory()->undefined_value(); | 
 |   return Execution::Call(isolate, function, receiver, nof_args, call_args); | 
 | } | 
 |  | 
 | void CheckDescriptorArrayLookups(Isolate* isolate, Handle<Map> map, | 
 |                                  std::vector<Handle<Name>>& names, | 
 |                                  Handle<JSFunction> csa_lookup) { | 
 |   // Test C++ implementation. | 
 |   { | 
 |     DisallowHeapAllocation no_gc; | 
 |     DescriptorArray descriptors = map->instance_descriptors(kRelaxedLoad); | 
 |     DCHECK(descriptors.IsSortedNoDuplicates()); | 
 |     int nof_descriptors = descriptors.number_of_descriptors(); | 
 |  | 
 |     for (size_t i = 0; i < names.size(); ++i) { | 
 |       Name name = *names[i]; | 
 |       InternalIndex index = descriptors.Search(name, nof_descriptors, false); | 
 |       CHECK(index.is_found()); | 
 |       CHECK_EQ(i, index.as_uint32()); | 
 |     } | 
 |   } | 
 |  | 
 |   // Test CSA implementation. | 
 |   if (!FLAG_jitless) { | 
 |     for (size_t i = 0; i < names.size(); ++i) { | 
 |       Handle<Object> name_index = | 
 |           Call(isolate, csa_lookup, map, names[i]).ToHandleChecked(); | 
 |       CHECK(name_index->IsSmi()); | 
 |       CHECK_EQ(DescriptorArray::ToKeyIndex(static_cast<int>(i)), | 
 |                Smi::ToInt(*name_index)); | 
 |     } | 
 |   } | 
 | } | 
 |  | 
 | void CheckTransitionArrayLookups(Isolate* isolate, | 
 |                                  Handle<TransitionArray> transitions, | 
 |                                  std::vector<Handle<Map>>& maps, | 
 |                                  Handle<JSFunction> csa_lookup) { | 
 |   // Test C++ implementation. | 
 |   { | 
 |     DisallowHeapAllocation no_gc; | 
 |     DCHECK(transitions->IsSortedNoDuplicates()); | 
 |  | 
 |     for (size_t i = 0; i < maps.size(); ++i) { | 
 |       Map expected_map = *maps[i]; | 
 |       Name name = expected_map.instance_descriptors(kRelaxedLoad) | 
 |                       .GetKey(expected_map.LastAdded()); | 
 |  | 
 |       Map map = transitions->SearchAndGetTargetForTesting(PropertyKind::kData, | 
 |                                                           name, NONE); | 
 |       CHECK(!map.is_null()); | 
 |       CHECK_EQ(expected_map, map); | 
 |     } | 
 |   } | 
 |  | 
 |   // Test CSA implementation. | 
 |   if (!FLAG_jitless) { | 
 |     for (size_t i = 0; i < maps.size(); ++i) { | 
 |       Handle<Map> expected_map = maps[i]; | 
 |       Handle<Name> name(expected_map->instance_descriptors(kRelaxedLoad) | 
 |                             .GetKey(expected_map->LastAdded()), | 
 |                         isolate); | 
 |  | 
 |       Handle<Object> transition_map = | 
 |           Call(isolate, csa_lookup, transitions, name).ToHandleChecked(); | 
 |       CHECK(transition_map->IsMap()); | 
 |       CHECK_EQ(*expected_map, *transition_map); | 
 |     } | 
 |   } | 
 | } | 
 |  | 
 | // Creates function with (Map, Name) arguments. Returns Smi with the index of | 
 | // the name value of the found descriptor (DescriptorArray::ToKeyIndex()) | 
 | // or null otherwise. | 
 | Handle<JSFunction> CreateCsaDescriptorArrayLookup(Isolate* isolate) { | 
 |   // We are not allowed to generate code in jitless mode. | 
 |   if (FLAG_jitless) return Handle<JSFunction>(); | 
 |  | 
 |   // Preallocate handle for the result in the current handle scope. | 
 |   Handle<JSFunction> result_function(JSFunction{}, isolate); | 
 |  | 
 |   const int kNumParams = 2; | 
 |  | 
 |   compiler::CodeAssemblerTester asm_tester( | 
 |       isolate, kNumParams + 1,  // +1 to include receiver. | 
 |       CodeKind::FOR_TESTING); | 
 |   { | 
 |     CodeStubAssembler m(asm_tester.state()); | 
 |  | 
 |     auto map = m.Parameter<Map>(1); | 
 |     auto unique_name = m.Parameter<Name>(2); | 
 |  | 
 |     Label passed(&m), failed(&m); | 
 |     Label if_found(&m), if_not_found(&m); | 
 |     TVariable<IntPtrT> var_name_index(&m); | 
 |  | 
 |     TNode<Uint32T> bit_field3 = m.LoadMapBitField3(map); | 
 |     TNode<DescriptorArray> descriptors = m.LoadMapDescriptors(map); | 
 |  | 
 |     m.DescriptorLookup(unique_name, descriptors, bit_field3, &if_found, | 
 |                        &var_name_index, &if_not_found); | 
 |  | 
 |     m.BIND(&if_found); | 
 |     m.Return(m.SmiTag(var_name_index.value())); | 
 |  | 
 |     m.BIND(&if_not_found); | 
 |     m.Return(m.NullConstant()); | 
 |   } | 
 |  | 
 |   { | 
 |     compiler::FunctionTester ft(asm_tester.GenerateCode(), kNumParams); | 
 |     // Copy function value to a handle created in the outer handle scope. | 
 |     result_function.PatchValue(*ft.function); | 
 |   } | 
 |  | 
 |   return result_function; | 
 | } | 
 |  | 
 | // Creates function with (TransitionArray, Name) arguments. Returns transition | 
 | // map if transition is found or null otherwise. | 
 | Handle<JSFunction> CreateCsaTransitionArrayLookup(Isolate* isolate) { | 
 |   // We are not allowed to generate code in jitless mode. | 
 |   if (FLAG_jitless) return Handle<JSFunction>(); | 
 |  | 
 |   // Preallocate handle for the result in the current handle scope. | 
 |   Handle<JSFunction> result_function(JSFunction{}, isolate); | 
 |  | 
 |   const int kNumParams = 2; | 
 |   compiler::CodeAssemblerTester asm_tester( | 
 |       isolate, kNumParams + 1,  // +1 to include receiver. | 
 |       CodeKind::FOR_TESTING); | 
 |   { | 
 |     CodeStubAssembler m(asm_tester.state()); | 
 |  | 
 |     auto transitions = m.Parameter<TransitionArray>(1); | 
 |     auto unique_name = m.Parameter<Name>(2); | 
 |  | 
 |     Label passed(&m), failed(&m); | 
 |     Label if_found(&m), if_not_found(&m); | 
 |     TVariable<IntPtrT> var_name_index(&m); | 
 |  | 
 |     m.TransitionLookup(unique_name, transitions, &if_found, &var_name_index, | 
 |                        &if_not_found); | 
 |  | 
 |     m.BIND(&if_found); | 
 |     { | 
 |       STATIC_ASSERT(kData == 0); | 
 |       STATIC_ASSERT(NONE == 0); | 
 |       const int kKeyToTargetOffset = (TransitionArray::kEntryTargetIndex - | 
 |                                       TransitionArray::kEntryKeyIndex) * | 
 |                                      kTaggedSize; | 
 |       TNode<Map> transition_map = m.CAST(m.GetHeapObjectAssumeWeak( | 
 |           m.LoadArrayElement(transitions, WeakFixedArray::kHeaderSize, | 
 |                              var_name_index.value(), kKeyToTargetOffset))); | 
 |       m.Return(transition_map); | 
 |     } | 
 |  | 
 |     m.BIND(&if_not_found); | 
 |     m.Return(m.NullConstant()); | 
 |   } | 
 |  | 
 |   { | 
 |     compiler::FunctionTester ft(asm_tester.GenerateCode(), kNumParams); | 
 |     // Copy function value to a handle created in the outer handle scope. | 
 |     result_function.PatchValue(*ft.function); | 
 |   } | 
 |  | 
 |   return result_function; | 
 | } | 
 |  | 
 | }  // namespace | 
 |  | 
 | TEST(DescriptorArrayHashCollisionMassive) { | 
 |   CcTest::InitializeVM(); | 
 |   Isolate* isolate = CcTest::i_isolate(); | 
 |   HandleScope handle_scope(isolate); | 
 |  | 
 |   static_assert(Name::kNofHashBitFields == 2, "This test needs updating"); | 
 |  | 
 |   std::vector<Handle<Name>> names; | 
 |  | 
 |   // Use the same hash value for all names. | 
 |   uint32_t hash = | 
 |       static_cast<uint32_t>(isolate->GenerateIdentityHash(Name::kHashBitMask)); | 
 |  | 
 |   for (int i = 0; i < kMaxNumberOfDescriptors / 2; ++i) { | 
 |     // Add pairs of names having the same base hash value but having different | 
 |     // values of is_integer bit. | 
 |     bool first_is_integer = (i & 1) != 0; | 
 |     bool second_is_integer = (i & 2) != 0; | 
 |  | 
 |     names.push_back(NewNameWithHash(isolate, "a", hash, first_is_integer)); | 
 |     names.push_back(NewNameWithHash(isolate, "b", hash, second_is_integer)); | 
 |   } | 
 |  | 
 |   // Create descriptor array with the created names by appending fields to some | 
 |   // map. DescriptorArray marking relies on the fact that it's attached to an | 
 |   // owning map. | 
 |   Handle<Map> map = Map::Create(isolate, 0); | 
 |  | 
 |   Handle<FieldType> any_type = FieldType::Any(isolate); | 
 |  | 
 |   for (size_t i = 0; i < names.size(); ++i) { | 
 |     map = Map::CopyWithField(isolate, map, names[i], any_type, NONE, | 
 |                              PropertyConstness::kMutable, | 
 |                              Representation::Tagged(), OMIT_TRANSITION) | 
 |               .ToHandleChecked(); | 
 |   } | 
 |  | 
 |   Handle<JSFunction> csa_lookup = CreateCsaDescriptorArrayLookup(isolate); | 
 |  | 
 |   CheckDescriptorArrayLookups(isolate, map, names, csa_lookup); | 
 |  | 
 |   // Sort descriptor array and check it again. | 
 |   map->instance_descriptors(kRelaxedLoad).Sort(); | 
 |   CheckDescriptorArrayLookups(isolate, map, names, csa_lookup); | 
 | } | 
 |  | 
 | TEST(DescriptorArrayHashCollision) { | 
 |   CcTest::InitializeVM(); | 
 |   Isolate* isolate = CcTest::i_isolate(); | 
 |   HandleScope handle_scope(isolate); | 
 |  | 
 |   static_assert(Name::kNofHashBitFields == 2, "This test needs updating"); | 
 |  | 
 |   std::vector<Handle<Name>> names; | 
 |   uint32_t hash = 0; | 
 |  | 
 |   for (int i = 0; i < kMaxNumberOfDescriptors / 2; ++i) { | 
 |     if (i % 2 == 0) { | 
 |       // Change hash value for every pair of names. | 
 |       hash = static_cast<uint32_t>( | 
 |           isolate->GenerateIdentityHash(Name::kHashBitMask)); | 
 |     } | 
 |  | 
 |     // Add pairs of names having the same base hash value but having different | 
 |     // values of is_integer bit. | 
 |     bool first_is_integer = (i & 1) != 0; | 
 |     bool second_is_integer = (i & 2) != 0; | 
 |  | 
 |     names.push_back(NewNameWithHash(isolate, "a", hash, first_is_integer)); | 
 |     names.push_back(NewNameWithHash(isolate, "b", hash, second_is_integer)); | 
 |   } | 
 |  | 
 |   // Create descriptor array with the created names by appending fields to some | 
 |   // map. DescriptorArray marking relies on the fact that it's attached to an | 
 |   // owning map. | 
 |   Handle<Map> map = Map::Create(isolate, 0); | 
 |  | 
 |   Handle<FieldType> any_type = FieldType::Any(isolate); | 
 |  | 
 |   for (size_t i = 0; i < names.size(); ++i) { | 
 |     map = Map::CopyWithField(isolate, map, names[i], any_type, NONE, | 
 |                              PropertyConstness::kMutable, | 
 |                              Representation::Tagged(), OMIT_TRANSITION) | 
 |               .ToHandleChecked(); | 
 |   } | 
 |  | 
 |   Handle<JSFunction> csa_lookup = CreateCsaDescriptorArrayLookup(isolate); | 
 |  | 
 |   CheckDescriptorArrayLookups(isolate, map, names, csa_lookup); | 
 |  | 
 |   // Sort descriptor array and check it again. | 
 |   map->instance_descriptors(kRelaxedLoad).Sort(); | 
 |   CheckDescriptorArrayLookups(isolate, map, names, csa_lookup); | 
 | } | 
 |  | 
 | TEST(TransitionArrayHashCollisionMassive) { | 
 |   CcTest::InitializeVM(); | 
 |   Isolate* isolate = CcTest::i_isolate(); | 
 |   HandleScope handle_scope(isolate); | 
 |  | 
 |   static_assert(Name::kNofHashBitFields == 2, "This test needs updating"); | 
 |  | 
 |   std::vector<Handle<Name>> names; | 
 |  | 
 |   // Use the same hash value for all names. | 
 |   uint32_t hash = | 
 |       static_cast<uint32_t>(isolate->GenerateIdentityHash(Name::kHashBitMask)); | 
 |  | 
 |   for (int i = 0; i < TransitionsAccessor::kMaxNumberOfTransitions / 2; ++i) { | 
 |     // Add pairs of names having the same base hash value but having different | 
 |     // values of is_integer bit. | 
 |     bool first_is_integer = (i & 1) != 0; | 
 |     bool second_is_integer = (i & 2) != 0; | 
 |  | 
 |     names.push_back(NewNameWithHash(isolate, "a", hash, first_is_integer)); | 
 |     names.push_back(NewNameWithHash(isolate, "b", hash, second_is_integer)); | 
 |   } | 
 |  | 
 |   // Create transitions for each name. | 
 |   Handle<Map> root_map = Map::Create(isolate, 0); | 
 |  | 
 |   std::vector<Handle<Map>> maps; | 
 |  | 
 |   Handle<FieldType> any_type = FieldType::Any(isolate); | 
 |  | 
 |   for (size_t i = 0; i < names.size(); ++i) { | 
 |     Handle<Map> map = | 
 |         Map::CopyWithField(isolate, root_map, names[i], any_type, NONE, | 
 |                            PropertyConstness::kMutable, | 
 |                            Representation::Tagged(), INSERT_TRANSITION) | 
 |             .ToHandleChecked(); | 
 |     maps.push_back(map); | 
 |   } | 
 |  | 
 |   Handle<JSFunction> csa_lookup = CreateCsaTransitionArrayLookup(isolate); | 
 |  | 
 |   Handle<TransitionArray> transition_array( | 
 |       TestTransitionsAccessor(isolate, root_map).transitions(), isolate); | 
 |  | 
 |   CheckTransitionArrayLookups(isolate, transition_array, maps, csa_lookup); | 
 |  | 
 |   // Sort transition array and check it again. | 
 |   transition_array->Sort(); | 
 |   CheckTransitionArrayLookups(isolate, transition_array, maps, csa_lookup); | 
 | } | 
 |  | 
 | TEST(TransitionArrayHashCollision) { | 
 |   CcTest::InitializeVM(); | 
 |   Isolate* isolate = CcTest::i_isolate(); | 
 |   HandleScope handle_scope(isolate); | 
 |  | 
 |   static_assert(Name::kNofHashBitFields == 2, "This test needs updating"); | 
 |  | 
 |   std::vector<Handle<Name>> names; | 
 |  | 
 |   // Use the same hash value for all names. | 
 |   uint32_t hash = | 
 |       static_cast<uint32_t>(isolate->GenerateIdentityHash(Name::kHashBitMask)); | 
 |  | 
 |   for (int i = 0; i < TransitionsAccessor::kMaxNumberOfTransitions / 2; ++i) { | 
 |     if (i % 2 == 0) { | 
 |       // Change hash value for every pair of names. | 
 |       hash = static_cast<uint32_t>( | 
 |           isolate->GenerateIdentityHash(Name::kHashBitMask)); | 
 |     } | 
 |     // Add pairs of names having the same base hash value but having different | 
 |     // values of is_integer bit. | 
 |     bool first_is_integer = (i & 1) != 0; | 
 |     bool second_is_integer = (i & 2) != 0; | 
 |  | 
 |     names.push_back(NewNameWithHash(isolate, "a", hash, first_is_integer)); | 
 |     names.push_back(NewNameWithHash(isolate, "b", hash, second_is_integer)); | 
 |   } | 
 |  | 
 |   // Create transitions for each name. | 
 |   Handle<Map> root_map = Map::Create(isolate, 0); | 
 |  | 
 |   std::vector<Handle<Map>> maps; | 
 |  | 
 |   Handle<FieldType> any_type = FieldType::Any(isolate); | 
 |  | 
 |   for (size_t i = 0; i < names.size(); ++i) { | 
 |     Handle<Map> map = | 
 |         Map::CopyWithField(isolate, root_map, names[i], any_type, NONE, | 
 |                            PropertyConstness::kMutable, | 
 |                            Representation::Tagged(), INSERT_TRANSITION) | 
 |             .ToHandleChecked(); | 
 |     maps.push_back(map); | 
 |   } | 
 |  | 
 |   Handle<JSFunction> csa_lookup = CreateCsaTransitionArrayLookup(isolate); | 
 |  | 
 |   Handle<TransitionArray> transition_array( | 
 |       TestTransitionsAccessor(isolate, root_map).transitions(), isolate); | 
 |  | 
 |   CheckTransitionArrayLookups(isolate, transition_array, maps, csa_lookup); | 
 |  | 
 |   // Sort transition array and check it again. | 
 |   transition_array->Sort(); | 
 |   CheckTransitionArrayLookups(isolate, transition_array, maps, csa_lookup); | 
 | } | 
 |  | 
 | }  // namespace internal | 
 | }  // namespace v8 |