| // 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 <stdlib.h> |
| #include <sstream> |
| #include <utility> |
| |
| #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<JSObject> obj, |
| bool has_fast_properties) { |
| CHECK_EQ(has_fast_properties, obj->HasFastProperties()); |
| CHECK_EQ(ReadOnlyRoots(isolate).undefined_value(), obj->GetHash()); |
| Handle<OrderedHashSet> set = isolate->factory()->NewOrderedHashSet(); |
| OrderedHashSet::Add(isolate, set, obj); |
| CHECK_EQ(has_fast_properties, obj->HasFastProperties()); |
| return Smi::ToInt(obj->GetHash()); |
| } |
| |
| void CheckFastObject(Handle<JSObject> 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<JSObject> 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<JSObject> 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<JSObject> obj = GetGlobal<JSObject>("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<JSObject> obj = GetGlobal<JSObject>("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<JSObject> obj = |
| isolate->factory()->NewJSObject(isolate->object_function()); |
| CHECK(obj->HasFastProperties()); |
| JSObject::NormalizeProperties(isolate, 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<JSObject> obj = GetGlobal<JSObject>("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<JSObject> obj = GetGlobal<JSObject>("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<JSObject> obj = GetGlobal<JSObject>("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(isolate, 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<JSObject> obj = GetGlobal<JSObject>("x"); |
| JSObject::NormalizeProperties(isolate, 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<JSObject> obj = |
| isolate->factory()->NewJSObject(isolate->object_function()); |
| JSObject::NormalizeProperties(isolate, 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<JSObject> obj = GetGlobal<JSObject>("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 { |
| |
| using HashFunction = uint32_t (*)(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<uint64_t>(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 |