// Copyright 2017 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 #include "src/init/v8.h" #include "src/objects/objects-inl.h" #include "src/objects/objects.h" #include "src/objects/ordered-hash-table.h" #include "src/third_party/siphash/halfsiphash.h" #include "src/utils/utils.h" #include "test/cctest/cctest.h" namespace v8 { namespace internal { int AddToSetAndGetHash(Isolate* isolate, Handle obj, bool has_fast_properties) { CHECK_EQ(has_fast_properties, obj->HasFastProperties()); CHECK_EQ(ReadOnlyRoots(isolate).undefined_value(), obj->GetHash()); Handle set = isolate->factory()->NewOrderedHashSet(); OrderedHashSet::Add(isolate, set, obj); CHECK_EQ(has_fast_properties, obj->HasFastProperties()); return Smi::ToInt(obj->GetHash()); } void CheckFastObject(Handle obj, int hash) { CHECK(obj->HasFastProperties()); CHECK(obj->raw_properties_or_hash().IsPropertyArray()); CHECK_EQ(Smi::FromInt(hash), obj->GetHash()); CHECK_EQ(hash, obj->property_array().Hash()); } void CheckDictionaryObject(Handle obj, int hash) { CHECK(!obj->HasFastProperties()); CHECK(obj->raw_properties_or_hash().IsNameDictionary()); CHECK_EQ(Smi::FromInt(hash), obj->GetHash()); CHECK_EQ(hash, obj->property_dictionary().Hash()); } TEST(AddHashCodeToFastObjectWithoutProperties) { CcTest::InitializeVM(); v8::HandleScope scope(CcTest::isolate()); Isolate* isolate = CcTest::i_isolate(); Handle obj = isolate->factory()->NewJSObject(isolate->object_function()); CHECK(obj->HasFastProperties()); int hash = AddToSetAndGetHash(isolate, obj, true); CHECK_EQ(Smi::FromInt(hash), obj->raw_properties_or_hash()); } TEST(AddHashCodeToFastObjectWithInObjectProperties) { CcTest::InitializeVM(); v8::HandleScope scope(CcTest::isolate()); Isolate* isolate = CcTest::i_isolate(); const char* source = " var x = { a: 1};"; CompileRun(source); Handle obj = GetGlobal("x"); CHECK_EQ(ReadOnlyRoots(isolate).empty_fixed_array(), obj->raw_properties_or_hash()); int hash = AddToSetAndGetHash(isolate, obj, true); CHECK_EQ(Smi::FromInt(hash), obj->raw_properties_or_hash()); } TEST(AddHashCodeToFastObjectWithPropertiesArray) { CcTest::InitializeVM(); v8::HandleScope scope(CcTest::isolate()); Isolate* isolate = CcTest::i_isolate(); const char* source = " var x = {}; " " x.a = 1; x.b = 2; x.c = 3; x.d = 4; x.e = 5; "; CompileRun(source); Handle obj = GetGlobal("x"); CHECK(obj->HasFastProperties()); int hash = AddToSetAndGetHash(isolate, obj, true); CheckFastObject(obj, hash); } TEST(AddHashCodeToSlowObject) { CcTest::InitializeVM(); v8::HandleScope scope(CcTest::isolate()); Isolate* isolate = CcTest::i_isolate(); Handle obj = isolate->factory()->NewJSObject(isolate->object_function()); CHECK(obj->HasFastProperties()); JSObject::NormalizeProperties(obj, CLEAR_INOBJECT_PROPERTIES, 0, "cctest/test-hashcode"); CHECK(obj->raw_properties_or_hash().IsNameDictionary()); int hash = AddToSetAndGetHash(isolate, obj, false); CheckDictionaryObject(obj, hash); } TEST(TransitionFastWithInObjectToFastWithPropertyArray) { CcTest::InitializeVM(); v8::HandleScope scope(CcTest::isolate()); Isolate* isolate = CcTest::i_isolate(); const char* source = " var x = { };" " x.a = 1; x.b = 2; x.c = 3; x.d = 4;"; CompileRun(source); Handle obj = GetGlobal("x"); CHECK(obj->HasFastProperties()); int hash = AddToSetAndGetHash(isolate, obj, true); CHECK_EQ(Smi::FromInt(hash), obj->raw_properties_or_hash()); int length = obj->property_array().length(); CompileRun("x.e = 5;"); CHECK(obj->property_array().length() > length); CheckFastObject(obj, hash); } TEST(TransitionFastWithPropertyArray) { CcTest::InitializeVM(); v8::HandleScope scope(CcTest::isolate()); Isolate* isolate = CcTest::i_isolate(); const char* source = " var x = { };" " x.a = 1; x.b = 2; x.c = 3; x.d = 4; x.e = 5; "; CompileRun(source); Handle obj = GetGlobal("x"); CHECK(obj->raw_properties_or_hash().IsPropertyArray()); int hash = AddToSetAndGetHash(isolate, obj, true); CHECK_EQ(hash, obj->property_array().Hash()); int length = obj->property_array().length(); CompileRun("x.f = 2; x.g = 5; x.h = 2"); CHECK(obj->property_array().length() > length); CheckFastObject(obj, hash); } TEST(TransitionFastWithPropertyArrayToSlow) { CcTest::InitializeVM(); v8::HandleScope scope(CcTest::isolate()); Isolate* isolate = CcTest::i_isolate(); const char* source = " var x = { };" " x.a = 1; x.b = 2; x.c = 3; x.d = 4; x.e = 5; "; CompileRun(source); Handle obj = GetGlobal("x"); CHECK(obj->raw_properties_or_hash().IsPropertyArray()); int hash = AddToSetAndGetHash(isolate, obj, true); CHECK(obj->raw_properties_or_hash().IsPropertyArray()); CHECK_EQ(hash, obj->property_array().Hash()); JSObject::NormalizeProperties(obj, KEEP_INOBJECT_PROPERTIES, 0, "cctest/test-hashcode"); CheckDictionaryObject(obj, hash); } TEST(TransitionSlowToSlow) { CcTest::InitializeVM(); v8::HandleScope scope(CcTest::isolate()); Isolate* isolate = CcTest::i_isolate(); const char* source = " var x = {}; "; CompileRun(source); Handle obj = GetGlobal("x"); JSObject::NormalizeProperties(obj, CLEAR_INOBJECT_PROPERTIES, 0, "cctest/test-hashcode"); CHECK(obj->raw_properties_or_hash().IsNameDictionary()); int hash = AddToSetAndGetHash(isolate, obj, false); CHECK_EQ(hash, obj->property_dictionary().Hash()); int length = obj->property_dictionary().length(); CompileRun("for(var i = 0; i < 10; i++) { x['f'+i] = i };"); CHECK(obj->property_dictionary().length() > length); CheckDictionaryObject(obj, hash); } TEST(TransitionSlowToFastWithoutProperties) { CcTest::InitializeVM(); v8::HandleScope scope(CcTest::isolate()); Isolate* isolate = CcTest::i_isolate(); Handle obj = isolate->factory()->NewJSObject(isolate->object_function()); JSObject::NormalizeProperties(obj, CLEAR_INOBJECT_PROPERTIES, 0, "cctest/test-hashcode"); CHECK(obj->raw_properties_or_hash().IsNameDictionary()); int hash = AddToSetAndGetHash(isolate, obj, false); CHECK_EQ(hash, obj->property_dictionary().Hash()); JSObject::MigrateSlowToFast(obj, 0, "cctest/test-hashcode"); CHECK_EQ(Smi::FromInt(hash), obj->GetHash()); } TEST(TransitionSlowToFastWithPropertyArray) { CcTest::InitializeVM(); v8::HandleScope scope(CcTest::isolate()); Isolate* isolate = CcTest::i_isolate(); const char* source = " var x = Object.create(null); " " for(var i = 0; i < 10; i++) { x['f'+i] = i }; "; CompileRun(source); Handle obj = GetGlobal("x"); CHECK(obj->raw_properties_or_hash().IsNameDictionary()); int hash = AddToSetAndGetHash(isolate, obj, false); CHECK_EQ(hash, obj->property_dictionary().Hash()); JSObject::MigrateSlowToFast(obj, 0, "cctest/test-hashcode"); CheckFastObject(obj, hash); } namespace { typedef uint32_t (*HashFunction)(uint32_t key, uint64_t seed); void TestIntegerHashQuality(const int samples_log2, int num_buckets_log2, uint64_t seed, double max_var, HashFunction hash_function) { int samples = 1 << samples_log2; int num_buckets = 1 << num_buckets_log2; int mean = samples / num_buckets; int* buckets = new int[num_buckets]; for (int i = 0; i < num_buckets; i++) buckets[i] = 0; for (int i = 0; i < samples; i++) { uint32_t hash = hash_function(i, seed); buckets[hash % num_buckets]++; } int sum_deviation = 0; for (int i = 0; i < num_buckets; i++) { int deviation = abs(buckets[i] - mean); sum_deviation += deviation * deviation; } delete[] buckets; double variation_coefficient = sqrt(sum_deviation * 1.0 / num_buckets) / mean; printf("samples: 1 << %2d, buckets: 1 << %2d, var_coeff: %0.3f\n", samples_log2, num_buckets_log2, variation_coefficient); CHECK_LT(variation_coefficient, max_var); } uint32_t HalfSipHash(uint32_t key, uint64_t seed) { return halfsiphash(key, seed); } uint32_t JenkinsHash(uint32_t key, uint64_t seed) { return ComputeLongHash(static_cast(key) ^ seed); } uint32_t DefaultHash(uint32_t key, uint64_t seed) { return ComputeSeededHash(key, seed); } } // anonymous namespace void TestIntegerHashQuality(HashFunction hash_function) { TestIntegerHashQuality(17, 13, 0x123456789ABCDEFU, 0.4, hash_function); TestIntegerHashQuality(16, 12, 0x123456789ABCDEFU, 0.4, hash_function); TestIntegerHashQuality(15, 11, 0xFEDCBA987654321U, 0.4, hash_function); TestIntegerHashQuality(14, 10, 0xFEDCBA987654321U, 0.4, hash_function); TestIntegerHashQuality(13, 9, 1, 0.4, hash_function); TestIntegerHashQuality(12, 8, 1, 0.4, hash_function); TestIntegerHashQuality(17, 10, 0x123456789ABCDEFU, 0.2, hash_function); TestIntegerHashQuality(16, 9, 0x123456789ABCDEFU, 0.2, hash_function); TestIntegerHashQuality(15, 8, 0xFEDCBA987654321U, 0.2, hash_function); TestIntegerHashQuality(14, 7, 0xFEDCBA987654321U, 0.2, hash_function); TestIntegerHashQuality(13, 6, 1, 0.2, hash_function); TestIntegerHashQuality(12, 5, 1, 0.2, hash_function); } TEST(HalfSipHashQuality) { TestIntegerHashQuality(HalfSipHash); } TEST(JenkinsHashQuality) { TestIntegerHashQuality(JenkinsHash); } TEST(DefaultHashQuality) { TestIntegerHashQuality(DefaultHash); } } // namespace internal } // namespace v8