| // 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 |