c5005009dc
... hashcode-unittest. Bug: v8:12781 Change-Id: I118ec1f2963abfaf6fbf738b97a984c9449d105d Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/3699498 Reviewed-by: Leszek Swirski <leszeks@chromium.org> Commit-Queue: 王澳 <wangao.james@bytedance.com> Cr-Commit-Position: refs/heads/main@{#81103}
307 lines
9.8 KiB
C++
307 lines
9.8 KiB
C++
// 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/unittests/test-utils.h"
|
|
#include "testing/gtest/include/gtest/gtest.h"
|
|
|
|
namespace v8 {
|
|
namespace internal {
|
|
|
|
class HashcodeTest : public TestWithContext {
|
|
public:
|
|
template <typename T>
|
|
inline Handle<T> GetGlobal(const char* name) {
|
|
Handle<String> str_name =
|
|
i_isolate()->factory()->InternalizeUtf8String(name);
|
|
|
|
Handle<Object> value =
|
|
Object::GetProperty(i_isolate(), i_isolate()->global_object(), str_name)
|
|
.ToHandleChecked();
|
|
return Handle<T>::cast(value);
|
|
}
|
|
|
|
int AddToSetAndGetHash(Handle<JSObject> obj, bool has_fast_properties) {
|
|
CHECK_EQ(has_fast_properties, obj->HasFastProperties());
|
|
CHECK_EQ(ReadOnlyRoots(i_isolate()).undefined_value(), obj->GetHash());
|
|
Handle<OrderedHashSet> set = i_isolate()->factory()->NewOrderedHashSet();
|
|
OrderedHashSet::Add(i_isolate(), set, obj);
|
|
CHECK_EQ(has_fast_properties, obj->HasFastProperties());
|
|
return Smi::ToInt(obj->GetHash());
|
|
}
|
|
|
|
int GetPropertyDictionaryHash(Handle<JSObject> obj) {
|
|
if (V8_ENABLE_SWISS_NAME_DICTIONARY_BOOL) {
|
|
return obj->property_dictionary_swiss().Hash();
|
|
} else {
|
|
return obj->property_dictionary().Hash();
|
|
}
|
|
}
|
|
|
|
int GetPropertyDictionaryLength(Handle<JSObject> obj) {
|
|
if (V8_ENABLE_SWISS_NAME_DICTIONARY_BOOL) {
|
|
return obj->property_dictionary_swiss().Capacity();
|
|
} else {
|
|
return obj->property_dictionary().length();
|
|
}
|
|
}
|
|
|
|
void CheckIsDictionaryModeObject(Handle<JSObject> obj) {
|
|
if (V8_ENABLE_SWISS_NAME_DICTIONARY_BOOL) {
|
|
CHECK(obj->raw_properties_or_hash().IsSwissNameDictionary());
|
|
} else {
|
|
CHECK(obj->raw_properties_or_hash().IsNameDictionary());
|
|
}
|
|
}
|
|
|
|
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());
|
|
CheckIsDictionaryModeObject(obj);
|
|
CHECK_EQ(Smi::FromInt(hash), obj->GetHash());
|
|
CHECK_EQ(hash, GetPropertyDictionaryHash(obj));
|
|
}
|
|
};
|
|
|
|
TEST_F(HashcodeTest, AddHashCodeToFastObjectWithoutProperties) {
|
|
Handle<JSObject> obj =
|
|
i_isolate()->factory()->NewJSObject(i_isolate()->object_function());
|
|
CHECK(obj->HasFastProperties());
|
|
|
|
int hash = AddToSetAndGetHash(obj, true);
|
|
CHECK_EQ(Smi::FromInt(hash), obj->raw_properties_or_hash());
|
|
}
|
|
|
|
TEST_F(HashcodeTest, AddHashCodeToFastObjectWithInObjectProperties) {
|
|
const char* source = " var x = { a: 1};";
|
|
RunJS(source);
|
|
|
|
Handle<JSObject> obj = GetGlobal<JSObject>("x");
|
|
CHECK_EQ(ReadOnlyRoots(i_isolate()).empty_fixed_array(),
|
|
obj->raw_properties_or_hash());
|
|
|
|
int hash = AddToSetAndGetHash(obj, true);
|
|
CHECK_EQ(Smi::FromInt(hash), obj->raw_properties_or_hash());
|
|
}
|
|
|
|
TEST_F(HashcodeTest, AddHashCodeToFastObjectWithPropertiesArray) {
|
|
const char* source =
|
|
" var x = {}; "
|
|
" x.a = 1; x.b = 2; x.c = 3; x.d = 4; x.e = 5; ";
|
|
RunJS(source);
|
|
|
|
Handle<JSObject> obj = GetGlobal<JSObject>("x");
|
|
CHECK(obj->HasFastProperties());
|
|
|
|
int hash = AddToSetAndGetHash(obj, true);
|
|
CheckFastObject(obj, hash);
|
|
}
|
|
|
|
TEST_F(HashcodeTest, AddHashCodeToSlowObject) {
|
|
Handle<JSObject> obj =
|
|
i_isolate()->factory()->NewJSObject(i_isolate()->object_function());
|
|
CHECK(obj->HasFastProperties());
|
|
JSObject::NormalizeProperties(i_isolate(), obj, CLEAR_INOBJECT_PROPERTIES, 0,
|
|
"cctest/test-hashcode");
|
|
|
|
CheckIsDictionaryModeObject(obj);
|
|
|
|
int hash = AddToSetAndGetHash(obj, false);
|
|
CheckDictionaryObject(obj, hash);
|
|
}
|
|
|
|
TEST_F(HashcodeTest, TransitionFastWithInObjectToFastWithPropertyArray) {
|
|
const char* source =
|
|
" var x = { };"
|
|
" x.a = 1; x.b = 2; x.c = 3; x.d = 4;";
|
|
RunJS(source);
|
|
|
|
Handle<JSObject> obj = GetGlobal<JSObject>("x");
|
|
CHECK(obj->HasFastProperties());
|
|
|
|
int hash = AddToSetAndGetHash(obj, true);
|
|
CHECK_EQ(Smi::FromInt(hash), obj->raw_properties_or_hash());
|
|
|
|
int length = obj->property_array().length();
|
|
RunJS("x.e = 5;");
|
|
CHECK(obj->property_array().length() > length);
|
|
CheckFastObject(obj, hash);
|
|
}
|
|
|
|
TEST_F(HashcodeTest, TransitionFastWithPropertyArray) {
|
|
const char* source =
|
|
" var x = { };"
|
|
" x.a = 1; x.b = 2; x.c = 3; x.d = 4; x.e = 5; ";
|
|
RunJS(source);
|
|
|
|
Handle<JSObject> obj = GetGlobal<JSObject>("x");
|
|
CHECK(obj->raw_properties_or_hash().IsPropertyArray());
|
|
|
|
int hash = AddToSetAndGetHash(obj, true);
|
|
CHECK_EQ(hash, obj->property_array().Hash());
|
|
|
|
int length = obj->property_array().length();
|
|
RunJS("x.f = 2; x.g = 5; x.h = 2");
|
|
CHECK(obj->property_array().length() > length);
|
|
CheckFastObject(obj, hash);
|
|
}
|
|
|
|
TEST_F(HashcodeTest, TransitionFastWithPropertyArrayToSlow) {
|
|
const char* source =
|
|
" var x = { };"
|
|
" x.a = 1; x.b = 2; x.c = 3; x.d = 4; x.e = 5; ";
|
|
RunJS(source);
|
|
|
|
Handle<JSObject> obj = GetGlobal<JSObject>("x");
|
|
CHECK(obj->raw_properties_or_hash().IsPropertyArray());
|
|
|
|
int hash = AddToSetAndGetHash(obj, true);
|
|
CHECK(obj->raw_properties_or_hash().IsPropertyArray());
|
|
CHECK_EQ(hash, obj->property_array().Hash());
|
|
|
|
JSObject::NormalizeProperties(i_isolate(), obj, KEEP_INOBJECT_PROPERTIES, 0,
|
|
"cctest/test-hashcode");
|
|
CheckDictionaryObject(obj, hash);
|
|
}
|
|
|
|
TEST_F(HashcodeTest, TransitionSlowToSlow) {
|
|
const char* source = " var x = {}; ";
|
|
RunJS(source);
|
|
|
|
Handle<JSObject> obj = GetGlobal<JSObject>("x");
|
|
JSObject::NormalizeProperties(i_isolate(), obj, CLEAR_INOBJECT_PROPERTIES, 0,
|
|
"cctest/test-hashcode");
|
|
CheckIsDictionaryModeObject(obj);
|
|
|
|
int hash = AddToSetAndGetHash(obj, false);
|
|
CHECK_EQ(hash, GetPropertyDictionaryHash(obj));
|
|
|
|
int length = GetPropertyDictionaryLength(obj);
|
|
RunJS("for(var i = 0; i < 10; i++) { x['f'+i] = i };");
|
|
CHECK(GetPropertyDictionaryLength(obj) > length);
|
|
CheckDictionaryObject(obj, hash);
|
|
}
|
|
|
|
TEST_F(HashcodeTest, TransitionSlowToFastWithoutProperties) {
|
|
Handle<JSObject> obj =
|
|
i_isolate()->factory()->NewJSObject(i_isolate()->object_function());
|
|
JSObject::NormalizeProperties(i_isolate(), obj, CLEAR_INOBJECT_PROPERTIES, 0,
|
|
"cctest/test-hashcode");
|
|
CheckIsDictionaryModeObject(obj);
|
|
|
|
int hash = AddToSetAndGetHash(obj, false);
|
|
CHECK_EQ(hash, GetPropertyDictionaryHash(obj));
|
|
|
|
JSObject::MigrateSlowToFast(obj, 0, "cctest/test-hashcode");
|
|
CHECK_EQ(Smi::FromInt(hash), obj->GetHash());
|
|
}
|
|
|
|
TEST_F(HashcodeTest, TransitionSlowToFastWithPropertyArray) {
|
|
const char* source =
|
|
" var x = Object.create(null); "
|
|
" for(var i = 0; i < 10; i++) { x['f'+i] = i }; ";
|
|
RunJS(source);
|
|
|
|
Handle<JSObject> obj = GetGlobal<JSObject>("x");
|
|
CheckIsDictionaryModeObject(obj);
|
|
|
|
int hash = AddToSetAndGetHash(obj, false);
|
|
CHECK_EQ(hash, GetPropertyDictionaryHash(obj));
|
|
|
|
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_F(HashcodeTest, HalfSipHashQuality) {
|
|
TestIntegerHashQuality(HalfSipHash);
|
|
}
|
|
|
|
TEST_F(HashcodeTest, JenkinsHashQuality) {
|
|
TestIntegerHashQuality(JenkinsHash);
|
|
}
|
|
|
|
TEST_F(HashcodeTest, DefaultHashQuality) {
|
|
TestIntegerHashQuality(DefaultHash);
|
|
}
|
|
|
|
} // namespace internal
|
|
} // namespace v8
|