// 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-inl.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/function-tester.h" #include "test/cctest/test-transitions.h" #include "test/common/code-assembler-tester.h" namespace v8 { namespace internal { #include "src/codegen/define-code-stub-assembler-macros.inc" namespace { using Label = compiler::CodeAssemblerLabel; template using TVariable = compiler::TypedCodeAssemblerVariable; Handle NewNameWithHash(Isolate* isolate, const char* str, uint32_t hash, bool is_integer) { uint32_t hash_field = Name::CreateHashFieldValue( hash, is_integer ? Name::HashFieldType::kIntegerIndex : Name::HashFieldType::kHash); Handle name = isolate->factory()->NewOneByteInternalizedString( base::OneByteVector(str), hash_field); name->set_raw_hash_field(hash_field); CHECK(IsUniqueName(*name)); return name; } template MaybeHandle Call(Isolate* isolate, DirectHandle function, Args... args) { const int nof_args = sizeof...(Args); DirectHandle call_args[] = {args...}; DirectHandle receiver = isolate->factory()->undefined_value(); return Execution::Call(isolate, function, receiver, {call_args, nof_args}); } void CheckDescriptorArrayLookups(Isolate* isolate, Handle map, std::vector>& names, DirectHandle csa_lookup) { // Test C++ implementation. { DisallowGarbageCollection no_gc; Tagged descriptors = map->instance_descriptors(isolate); DCHECK(descriptors->IsSortedNoDuplicates()); int nof_descriptors = descriptors->number_of_descriptors(); for (size_t i = 0; i < names.size(); ++i) { Tagged 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 (!v8_flags.jitless) { for (size_t i = 0; i < names.size(); ++i) { DirectHandle name_index = Call(isolate, csa_lookup, map, names[i]).ToHandleChecked(); CHECK(IsSmi(*name_index)); CHECK_EQ(DescriptorArray::ToKeyIndex(static_cast(i)), Smi::ToInt(*name_index)); } } } void CheckTransitionArrayLookups(Isolate* isolate, Handle transitions, std::vector>& maps, DirectHandle csa_lookup) { // Test C++ implementation. { DisallowGarbageCollection no_gc; DCHECK(transitions->IsSortedNoDuplicates()); for (size_t i = 0; i < maps.size(); ++i) { Tagged expected_map = *maps[i]; Tagged name = expected_map->instance_descriptors(isolate)->GetKey( expected_map->LastAdded()); Tagged map = transitions->SearchAndGetTargetForTesting( PropertyKind::kData, name, NONE); CHECK(!map.is_null()); CHECK_EQ(expected_map, map); } } // Test CSA implementation. if (!v8_flags.jitless) { for (size_t i = 0; i < maps.size(); ++i) { DirectHandle expected_map = maps[i]; Handle name(expected_map->instance_descriptors(isolate)->GetKey( expected_map->LastAdded()), isolate); DirectHandle transition_map = Call(isolate, csa_lookup, transitions, name).ToHandleChecked(); CHECK(IsMap(*transition_map)); 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. DirectHandle CreateCsaDescriptorArrayLookup(Isolate* isolate) { // We are not allowed to generate code in jitless mode. if (v8_flags.jitless) return DirectHandle(); // Preallocate handle for the result in the current handle scope. Handle result_function(JSFunction{}, isolate); const int kNumParams = 2; compiler::CodeAssemblerTester asm_tester( isolate, JSParameterCount(kNumParams), CodeKind::FOR_TESTING); { CodeStubAssembler m(asm_tester.state()); auto map = m.Parameter(1); auto unique_name = m.Parameter(2); Label passed(&m), failed(&m); Label if_found(&m), if_not_found(&m); TVariable var_name_index(&m); TNode bit_field3 = m.LoadMapBitField3(map); TNode 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. DirectHandle CreateCsaTransitionArrayLookup(Isolate* isolate) { // We are not allowed to generate code in jitless mode. if (v8_flags.jitless) return DirectHandle(); // Preallocate handle for the result in the current handle scope. Handle result_function(JSFunction{}, isolate); const int kNumParams = 2; compiler::CodeAssemblerTester asm_tester( isolate, JSParameterCount(kNumParams), CodeKind::FOR_TESTING); { CodeStubAssembler m(asm_tester.state()); auto transitions = m.Parameter(1); auto unique_name = m.Parameter(2); Label passed(&m), failed(&m); Label if_found(&m), if_not_found(&m); TVariable var_name_index(&m); m.TransitionLookup(unique_name, transitions, &if_found, &var_name_index, &if_not_found); m.BIND(&if_found); { static_assert(static_cast(PropertyKind::kData) == 0); static_assert(NONE == 0); const int kKeyToTargetOffset = (TransitionArray::kEntryTargetIndex - TransitionArray::kEntryKeyIndex) * kTaggedSize; TNode transition_map = m.CAST(m.GetHeapObjectAssumeWeak( m.LoadArrayElement(transitions, OFFSET_OF_DATA_START(WeakFixedArray), 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::HashFieldTypeBits::kSize == 2, "This test might require updating if more HashFieldType values " "are introduced"); std::vector> names; // Use the same hash value for all names. uint32_t hash = static_cast( isolate->GenerateIdentityHash(Name::HashBits::kMax)); 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::Create(isolate, 0); DirectHandle 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(); } DirectHandle csa_lookup = CreateCsaDescriptorArrayLookup(isolate); CheckDescriptorArrayLookups(isolate, map, names, csa_lookup); // Sort descriptor array and check it again. map->instance_descriptors(isolate)->Sort(); CheckDescriptorArrayLookups(isolate, map, names, csa_lookup); } TEST(DescriptorArrayHashCollision) { CcTest::InitializeVM(); Isolate* isolate = CcTest::i_isolate(); HandleScope handle_scope(isolate); static_assert(Name::HashFieldTypeBits::kSize == 2, "This test might require updating if more HashFieldType values " "are introduced"); std::vector> 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( isolate->GenerateIdentityHash(Name::HashBits::kMax)); } // 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::Create(isolate, 0); DirectHandle 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(); } DirectHandle csa_lookup = CreateCsaDescriptorArrayLookup(isolate); CheckDescriptorArrayLookups(isolate, map, names, csa_lookup); // Sort descriptor array and check it again. map->instance_descriptors(isolate)->Sort(); CheckDescriptorArrayLookups(isolate, map, names, csa_lookup); } TEST(TransitionArrayHashCollisionMassive) { CcTest::InitializeVM(); Isolate* isolate = CcTest::i_isolate(); HandleScope handle_scope(isolate); static_assert(Name::HashFieldTypeBits::kSize == 2, "This test might require updating if more HashFieldType values " "are introduced"); std::vector> names; // Use the same hash value for all names. uint32_t hash = static_cast( isolate->GenerateIdentityHash(Name::HashBits::kMax)); 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. DirectHandle root_map = Map::Create(isolate, 0); std::vector> maps; DirectHandle any_type = FieldType::Any(isolate); for (size_t i = 0; i < names.size(); ++i) { Handle map = Map::CopyWithField(isolate, root_map, names[i], any_type, NONE, PropertyConstness::kMutable, Representation::Tagged(), INSERT_TRANSITION) .ToHandleChecked(); maps.push_back(map); } DirectHandle csa_lookup = CreateCsaTransitionArrayLookup(isolate); Handle 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::HashFieldTypeBits::kSize == 2, "This test might require updating if more HashFieldType values " "are introduced"); std::vector> names; // Use the same hash value for all names. uint32_t hash = static_cast( isolate->GenerateIdentityHash(Name::HashBits::kMax)); for (int i = 0; i < TransitionsAccessor::kMaxNumberOfTransitions / 2; ++i) { if (i % 2 == 0) { // Change hash value for every pair of names. hash = static_cast( isolate->GenerateIdentityHash(Name::HashBits::kMax)); } // 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. DirectHandle root_map = Map::Create(isolate, 0); std::vector> maps; DirectHandle any_type = FieldType::Any(isolate); for (size_t i = 0; i < names.size(); ++i) { Handle map = Map::CopyWithField(isolate, root_map, names[i], any_type, NONE, PropertyConstness::kMutable, Representation::Tagged(), INSERT_TRANSITION) .ToHandleChecked(); maps.push_back(map); } DirectHandle csa_lookup = CreateCsaTransitionArrayLookup(isolate); Handle 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); } #include "src/codegen/undef-code-stub-assembler-macros.inc" } // namespace internal } // namespace v8