| // Copyright 2015 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 <set> |
| |
| #include "src/execution/isolate.h" |
| #include "src/heap/factory-inl.h" |
| #include "src/objects/heap-number-inl.h" |
| #include "src/utils/identity-map.h" |
| #include "src/objects/objects.h" |
| #include "src/zone/zone.h" |
| #include "test/cctest/cctest.h" |
| |
| namespace v8 { |
| namespace internal { |
| |
| // Helper for testing. A "friend" of the IdentityMapBase class, it is able to |
| // "move" objects to simulate GC for testing the internals of the map. |
| class IdentityMapTester : public HandleAndZoneScope { |
| public: |
| IdentityMap<void*, ZoneAllocationPolicy> map; |
| |
| IdentityMapTester() : map(heap(), ZoneAllocationPolicy(main_zone())) {} |
| |
| Heap* heap() { return isolate()->heap(); } |
| Isolate* isolate() { return main_isolate(); } |
| |
| void TestInsertFind(Handle<Object> key1, void* val1, Handle<Object> key2, |
| void* val2) { |
| CHECK_NULL(map.Find(key1)); |
| CHECK_NULL(map.Find(key2)); |
| |
| // Set {key1} the first time. |
| auto find_result = map.FindOrInsert(key1); |
| CHECK_NOT_NULL(find_result.entry); |
| CHECK(!find_result.already_exists); |
| *find_result.entry = val1; |
| |
| for (int i = 0; i < 3; i++) { // Get and find {key1} K times. |
| { |
| auto new_find_result = map.FindOrInsert(key1); |
| CHECK(new_find_result.already_exists); |
| CHECK_EQ(find_result.entry, new_find_result.entry); |
| CHECK_EQ(val1, *new_find_result.entry); |
| CHECK_NULL(map.Find(key2)); |
| } |
| { |
| void** nentry = map.Find(key1); |
| CHECK_EQ(find_result.entry, nentry); |
| CHECK_EQ(val1, *nentry); |
| CHECK_NULL(map.Find(key2)); |
| } |
| } |
| |
| // Set {key2} the first time. |
| auto find_result2 = map.FindOrInsert(key2); |
| CHECK_NOT_NULL(find_result2.entry); |
| CHECK(!find_result2.already_exists); |
| *find_result2.entry = val2; |
| |
| for (int i = 0; i < 3; i++) { // Get and find {key1} and {key2} K times. |
| { |
| auto new_find_result = map.FindOrInsert(key2); |
| CHECK_EQ(find_result2.entry, new_find_result.entry); |
| CHECK_EQ(val2, *new_find_result.entry); |
| } |
| { |
| void** nentry = map.Find(key2); |
| CHECK_EQ(find_result2.entry, nentry); |
| CHECK_EQ(val2, *nentry); |
| } |
| { |
| void** nentry = map.Find(key1); |
| CHECK_EQ(val1, *nentry); |
| } |
| } |
| } |
| |
| void TestFindDelete(Handle<Object> key1, void* val1, Handle<Object> key2, |
| void* val2) { |
| CHECK_NULL(map.Find(key1)); |
| CHECK_NULL(map.Find(key2)); |
| |
| // Set {key1} and {key2} for the first time. |
| auto find_result1 = map.FindOrInsert(key1); |
| CHECK(!find_result1.already_exists); |
| CHECK_NOT_NULL(find_result1.entry); |
| *find_result1.entry = val1; |
| auto find_result2 = map.FindOrInsert(key2); |
| CHECK(!find_result1.already_exists); |
| CHECK_NOT_NULL(find_result2.entry); |
| *find_result2.entry = val2; |
| |
| for (int i = 0; i < 3; i++) { // Find {key1} and {key2} 3 times. |
| { |
| void** nentry = map.Find(key2); |
| CHECK_EQ(val2, *nentry); |
| } |
| { |
| void** nentry = map.Find(key1); |
| CHECK_EQ(val1, *nentry); |
| } |
| } |
| |
| // Delete {key1} |
| void* deleted_entry_1; |
| CHECK(map.Delete(key1, &deleted_entry_1)); |
| CHECK_NOT_NULL(deleted_entry_1); |
| deleted_entry_1 = val1; |
| |
| for (int i = 0; i < 3; i++) { // Find {key1} and not {key2} 3 times. |
| { |
| void** nentry = map.Find(key1); |
| CHECK_NULL(nentry); |
| } |
| { |
| void** nentry = map.Find(key2); |
| CHECK_EQ(val2, *nentry); |
| } |
| } |
| |
| // Delete {key2} |
| void* deleted_entry_2; |
| CHECK(map.Delete(key2, &deleted_entry_2)); |
| CHECK_NOT_NULL(deleted_entry_2); |
| deleted_entry_2 = val2; |
| |
| for (int i = 0; i < 3; i++) { // Don't find {key1} and {key2} 3 times. |
| { |
| void** nentry = map.Find(key1); |
| CHECK_NULL(nentry); |
| } |
| { |
| void** nentry = map.Find(key2); |
| CHECK_NULL(nentry); |
| } |
| } |
| } |
| |
| Handle<Smi> smi(int value) { |
| return Handle<Smi>(Smi::FromInt(value), isolate()); |
| } |
| |
| Handle<Object> num(double value) { |
| return isolate()->factory()->NewNumber(value); |
| } |
| |
| void SimulateGCByIncrementingSmisBy(int shift) { |
| for (int i = 0; i < map.capacity_; i++) { |
| Address key = map.keys_[i]; |
| if (!Internals::HasHeapObjectTag(key)) { |
| map.keys_[i] = Internals::IntToSmi(Internals::SmiValue(key) + shift); |
| } |
| } |
| map.gc_counter_ = -1; |
| } |
| |
| void CheckFind(Handle<Object> key, void* value) { |
| void** entry = map.Find(key); |
| CHECK_NOT_NULL(entry); |
| CHECK_EQ(value, *entry); |
| } |
| |
| void CheckFindOrInsert(Handle<Object> key, void* value) { |
| auto find_result = map.FindOrInsert(key); |
| CHECK(find_result.already_exists); |
| CHECK_NOT_NULL(find_result.entry); |
| CHECK_EQ(value, *find_result.entry); |
| } |
| |
| void CheckDelete(Handle<Object> key, void* value) { |
| void* entry; |
| CHECK(map.Delete(key, &entry)); |
| CHECK_NOT_NULL(entry); |
| CHECK_EQ(value, entry); |
| } |
| |
| void PrintMap() { |
| PrintF("{\n"); |
| for (int i = 0; i < map.capacity_; i++) { |
| PrintF(" %3d: %p => %p\n", i, reinterpret_cast<void*>(map.keys_[i]), |
| reinterpret_cast<void*>(map.values_[i])); |
| } |
| PrintF("}\n"); |
| } |
| |
| void Resize() { map.Resize(map.capacity_ * 4); } |
| |
| void Rehash() { map.Rehash(); } |
| }; |
| |
| TEST(Find_smi_not_found) { |
| IdentityMapTester t; |
| for (int i = 0; i < 100; i++) { |
| CHECK_NULL(t.map.Find(t.smi(i))); |
| } |
| } |
| |
| |
| TEST(Find_num_not_found) { |
| IdentityMapTester t; |
| for (int i = 0; i < 100; i++) { |
| CHECK_NULL(t.map.Find(t.num(i + 0.2))); |
| } |
| } |
| |
| TEST(Delete_smi_not_found) { |
| IdentityMapTester t; |
| for (int i = 0; i < 100; i++) { |
| void* deleted_value = &t; |
| CHECK(!t.map.Delete(t.smi(i), &deleted_value)); |
| CHECK_EQ(&t, deleted_value); |
| } |
| } |
| |
| TEST(Delete_num_not_found) { |
| IdentityMapTester t; |
| for (int i = 0; i < 100; i++) { |
| void* deleted_value = &t; |
| CHECK(!t.map.Delete(t.num(i + 0.2), &deleted_value)); |
| CHECK_EQ(&t, deleted_value); |
| } |
| } |
| |
| TEST(GetFind_smi_0) { |
| IdentityMapTester t; |
| t.TestInsertFind(t.smi(0), t.isolate(), t.smi(1), t.heap()); |
| } |
| |
| TEST(GetFind_smi_13) { |
| IdentityMapTester t; |
| t.TestInsertFind(t.smi(13), t.isolate(), t.smi(17), t.heap()); |
| } |
| |
| TEST(GetFind_num_13) { |
| IdentityMapTester t; |
| t.TestInsertFind(t.num(13.1), t.isolate(), t.num(17.1), t.heap()); |
| } |
| |
| TEST(Delete_smi_13) { |
| IdentityMapTester t; |
| t.TestFindDelete(t.smi(13), t.isolate(), t.smi(17), t.heap()); |
| CHECK(t.map.empty()); |
| } |
| |
| TEST(Delete_num_13) { |
| IdentityMapTester t; |
| t.TestFindDelete(t.num(13.1), t.isolate(), t.num(17.1), t.heap()); |
| CHECK(t.map.empty()); |
| } |
| |
| TEST(GetFind_smi_17m) { |
| const int kInterval = 17; |
| const int kShift = 1099; |
| IdentityMapTester t; |
| |
| for (int i = 1; i < 100; i += kInterval) { |
| t.map.Insert(t.smi(i), reinterpret_cast<void*>(i + kShift)); |
| } |
| |
| for (int i = 1; i < 100; i += kInterval) { |
| t.CheckFind(t.smi(i), reinterpret_cast<void*>(i + kShift)); |
| } |
| |
| for (int i = 1; i < 100; i += kInterval) { |
| t.CheckFindOrInsert(t.smi(i), reinterpret_cast<void*>(i + kShift)); |
| } |
| |
| for (int i = 1; i < 100; i++) { |
| void** entry = t.map.Find(t.smi(i)); |
| if ((i % kInterval) != 1) { |
| CHECK_NULL(entry); |
| } else { |
| CHECK_NOT_NULL(entry); |
| CHECK_EQ(reinterpret_cast<void*>(i + kShift), *entry); |
| } |
| } |
| } |
| |
| TEST(Delete_smi_17m) { |
| const int kInterval = 17; |
| const int kShift = 1099; |
| IdentityMapTester t; |
| |
| for (int i = 1; i < 100; i += kInterval) { |
| t.map.Insert(t.smi(i), reinterpret_cast<void*>(i + kShift)); |
| } |
| |
| for (int i = 1; i < 100; i += kInterval) { |
| t.CheckFind(t.smi(i), reinterpret_cast<void*>(i + kShift)); |
| } |
| |
| for (int i = 1; i < 100; i += kInterval) { |
| t.CheckDelete(t.smi(i), reinterpret_cast<void*>(i + kShift)); |
| for (int j = 1; j < 100; j += kInterval) { |
| auto entry = t.map.Find(t.smi(j)); |
| if (j <= i) { |
| CHECK_NULL(entry); |
| } else { |
| CHECK_NOT_NULL(entry); |
| CHECK_EQ(reinterpret_cast<void*>(j + kShift), *entry); |
| } |
| } |
| } |
| } |
| |
| TEST(GetFind_num_1000) { |
| const int kPrime = 137; |
| IdentityMapTester t; |
| int val1; |
| int val2; |
| |
| for (int i = 0; i < 1000; i++) { |
| t.TestInsertFind(t.smi(i * kPrime), &val1, t.smi(i * kPrime + 1), &val2); |
| } |
| } |
| |
| TEST(Delete_num_1000) { |
| const int kPrime = 137; |
| IdentityMapTester t; |
| |
| for (int i = 0; i < 1000; i++) { |
| t.map.Insert(t.smi(i * kPrime), reinterpret_cast<void*>(i * kPrime)); |
| } |
| |
| // Delete every second value in reverse. |
| for (int i = 999; i >= 0; i -= 2) { |
| void* entry; |
| CHECK(t.map.Delete(t.smi(i * kPrime), &entry)); |
| CHECK_EQ(reinterpret_cast<void*>(i * kPrime), entry); |
| } |
| |
| for (int i = 0; i < 1000; i++) { |
| auto entry = t.map.Find(t.smi(i * kPrime)); |
| if (i % 2) { |
| CHECK_NULL(entry); |
| } else { |
| CHECK_NOT_NULL(entry); |
| CHECK_EQ(reinterpret_cast<void*>(i * kPrime), *entry); |
| } |
| } |
| |
| // Delete the rest. |
| for (int i = 0; i < 1000; i += 2) { |
| void* entry; |
| CHECK(t.map.Delete(t.smi(i * kPrime), &entry)); |
| CHECK_EQ(reinterpret_cast<void*>(i * kPrime), entry); |
| } |
| |
| for (int i = 0; i < 1000; i++) { |
| auto entry = t.map.Find(t.smi(i * kPrime)); |
| CHECK_NULL(entry); |
| } |
| } |
| |
| TEST(GetFind_smi_gc) { |
| const int kKey = 33; |
| const int kShift = 1211; |
| IdentityMapTester t; |
| |
| t.map.Insert(t.smi(kKey), &t); |
| t.SimulateGCByIncrementingSmisBy(kShift); |
| t.CheckFind(t.smi(kKey + kShift), &t); |
| t.CheckFindOrInsert(t.smi(kKey + kShift), &t); |
| } |
| |
| TEST(Delete_smi_gc) { |
| const int kKey = 33; |
| const int kShift = 1211; |
| IdentityMapTester t; |
| |
| t.map.Insert(t.smi(kKey), &t); |
| t.SimulateGCByIncrementingSmisBy(kShift); |
| t.CheckDelete(t.smi(kKey + kShift), &t); |
| } |
| |
| TEST(GetFind_smi_gc2) { |
| int kKey1 = 1; |
| int kKey2 = 33; |
| const int kShift = 1211; |
| IdentityMapTester t; |
| |
| t.map.Insert(t.smi(kKey1), &kKey1); |
| t.map.Insert(t.smi(kKey2), &kKey2); |
| t.SimulateGCByIncrementingSmisBy(kShift); |
| t.CheckFind(t.smi(kKey1 + kShift), &kKey1); |
| t.CheckFindOrInsert(t.smi(kKey1 + kShift), &kKey1); |
| t.CheckFind(t.smi(kKey2 + kShift), &kKey2); |
| t.CheckFindOrInsert(t.smi(kKey2 + kShift), &kKey2); |
| } |
| |
| TEST(Delete_smi_gc2) { |
| int kKey1 = 1; |
| int kKey2 = 33; |
| const int kShift = 1211; |
| IdentityMapTester t; |
| |
| t.map.Insert(t.smi(kKey1), &kKey1); |
| t.map.Insert(t.smi(kKey2), &kKey2); |
| t.SimulateGCByIncrementingSmisBy(kShift); |
| t.CheckDelete(t.smi(kKey1 + kShift), &kKey1); |
| t.CheckDelete(t.smi(kKey2 + kShift), &kKey2); |
| } |
| |
| TEST(GetFind_smi_gc_n) { |
| const int kShift = 12011; |
| IdentityMapTester t; |
| int keys[12] = {1, 2, 7, 8, 15, 23, |
| 1 + 32, 2 + 32, 7 + 32, 8 + 32, 15 + 32, 23 + 32}; |
| // Initialize the map first. |
| for (size_t i = 0; i < arraysize(keys); i += 2) { |
| t.TestInsertFind(t.smi(keys[i]), &keys[i], t.smi(keys[i + 1]), |
| &keys[i + 1]); |
| } |
| // Check the above initialization. |
| for (size_t i = 0; i < arraysize(keys); i++) { |
| t.CheckFind(t.smi(keys[i]), &keys[i]); |
| } |
| // Simulate a GC by "moving" the smis in the internal keys array. |
| t.SimulateGCByIncrementingSmisBy(kShift); |
| // Check that searching for the incremented smis finds the same values. |
| for (size_t i = 0; i < arraysize(keys); i++) { |
| t.CheckFind(t.smi(keys[i] + kShift), &keys[i]); |
| } |
| // Check that searching for the incremented smis gets the same values. |
| for (size_t i = 0; i < arraysize(keys); i++) { |
| t.CheckFindOrInsert(t.smi(keys[i] + kShift), &keys[i]); |
| } |
| } |
| |
| TEST(Delete_smi_gc_n) { |
| const int kShift = 12011; |
| IdentityMapTester t; |
| int keys[12] = {1, 2, 7, 8, 15, 23, |
| 1 + 32, 2 + 32, 7 + 32, 8 + 32, 15 + 32, 23 + 32}; |
| // Initialize the map first. |
| for (size_t i = 0; i < arraysize(keys); i++) { |
| t.map.Insert(t.smi(keys[i]), &keys[i]); |
| } |
| // Simulate a GC by "moving" the smis in the internal keys array. |
| t.SimulateGCByIncrementingSmisBy(kShift); |
| // Check that deleting for the incremented smis finds the same values. |
| for (size_t i = 0; i < arraysize(keys); i++) { |
| t.CheckDelete(t.smi(keys[i] + kShift), &keys[i]); |
| } |
| } |
| |
| TEST(GetFind_smi_num_gc_n) { |
| const int kShift = 12019; |
| IdentityMapTester t; |
| int smi_keys[] = {1, 2, 7, 15, 23}; |
| Handle<Object> num_keys[] = {t.num(1.1), t.num(2.2), t.num(3.3), t.num(4.4), |
| t.num(5.5), t.num(6.6), t.num(7.7), t.num(8.8), |
| t.num(9.9), t.num(10.1)}; |
| // Initialize the map first. |
| for (size_t i = 0; i < arraysize(smi_keys); i++) { |
| t.map.Insert(t.smi(smi_keys[i]), &smi_keys[i]); |
| } |
| for (size_t i = 0; i < arraysize(num_keys); i++) { |
| t.map.Insert(num_keys[i], &num_keys[i]); |
| } |
| // Check the above initialization. |
| for (size_t i = 0; i < arraysize(smi_keys); i++) { |
| t.CheckFind(t.smi(smi_keys[i]), &smi_keys[i]); |
| } |
| for (size_t i = 0; i < arraysize(num_keys); i++) { |
| t.CheckFind(num_keys[i], &num_keys[i]); |
| } |
| |
| // Simulate a GC by moving SMIs. |
| // Ironically the SMIs "move", but the heap numbers don't! |
| t.SimulateGCByIncrementingSmisBy(kShift); |
| |
| // Check that searching for the incremented smis finds the same values. |
| for (size_t i = 0; i < arraysize(smi_keys); i++) { |
| t.CheckFind(t.smi(smi_keys[i] + kShift), &smi_keys[i]); |
| t.CheckFindOrInsert(t.smi(smi_keys[i] + kShift), &smi_keys[i]); |
| } |
| |
| // Check that searching for the numbers finds the same values. |
| for (size_t i = 0; i < arraysize(num_keys); i++) { |
| t.CheckFind(num_keys[i], &num_keys[i]); |
| t.CheckFindOrInsert(num_keys[i], &num_keys[i]); |
| } |
| } |
| |
| TEST(Delete_smi_num_gc_n) { |
| const int kShift = 12019; |
| IdentityMapTester t; |
| int smi_keys[] = {1, 2, 7, 15, 23}; |
| Handle<Object> num_keys[] = {t.num(1.1), t.num(2.2), t.num(3.3), t.num(4.4), |
| t.num(5.5), t.num(6.6), t.num(7.7), t.num(8.8), |
| t.num(9.9), t.num(10.1)}; |
| // Initialize the map first. |
| for (size_t i = 0; i < arraysize(smi_keys); i++) { |
| t.map.Insert(t.smi(smi_keys[i]), &smi_keys[i]); |
| } |
| for (size_t i = 0; i < arraysize(num_keys); i++) { |
| t.map.Insert(num_keys[i], &num_keys[i]); |
| } |
| |
| // Simulate a GC by moving SMIs. |
| // Ironically the SMIs "move", but the heap numbers don't! |
| t.SimulateGCByIncrementingSmisBy(kShift); |
| |
| // Check that deleting for the incremented smis finds the same values. |
| for (size_t i = 0; i < arraysize(smi_keys); i++) { |
| t.CheckDelete(t.smi(smi_keys[i] + kShift), &smi_keys[i]); |
| } |
| |
| // Check that deleting the numbers finds the same values. |
| for (size_t i = 0; i < arraysize(num_keys); i++) { |
| t.CheckDelete(num_keys[i], &num_keys[i]); |
| } |
| } |
| |
| TEST(Delete_smi_resizes) { |
| const int kKeyCount = 1024; |
| const int kValueOffset = 27; |
| IdentityMapTester t; |
| |
| // Insert one element to initialize map. |
| t.map.Insert(t.smi(0), reinterpret_cast<void*>(kValueOffset)); |
| |
| int initial_capacity = t.map.capacity(); |
| CHECK_LT(initial_capacity, kKeyCount); |
| |
| // Insert another kKeyCount - 1 keys. |
| for (int i = 1; i < kKeyCount; i++) { |
| t.map.Insert(t.smi(i), reinterpret_cast<void*>(i + kValueOffset)); |
| } |
| |
| // Check capacity increased. |
| CHECK_GT(t.map.capacity(), initial_capacity); |
| CHECK_GE(t.map.capacity(), kKeyCount); |
| |
| // Delete all the keys. |
| for (int i = 0; i < kKeyCount; i++) { |
| t.CheckDelete(t.smi(i), reinterpret_cast<void*>(i + kValueOffset)); |
| } |
| |
| // Should resize back to initial capacity. |
| CHECK_EQ(t.map.capacity(), initial_capacity); |
| } |
| |
| TEST(Iterator_smi_num) { |
| IdentityMapTester t; |
| int smi_keys[] = {1, 2, 7, 15, 23}; |
| Handle<Object> num_keys[] = {t.num(1.1), t.num(2.2), t.num(3.3), t.num(4.4), |
| t.num(5.5), t.num(6.6), t.num(7.7), t.num(8.8), |
| t.num(9.9), t.num(10.1)}; |
| // Initialize the map. |
| for (size_t i = 0; i < arraysize(smi_keys); i++) { |
| t.map.Insert(t.smi(smi_keys[i]), reinterpret_cast<void*>(i)); |
| } |
| for (size_t i = 0; i < arraysize(num_keys); i++) { |
| t.map.Insert(num_keys[i], reinterpret_cast<void*>(i + 5)); |
| } |
| |
| // Check iterator sees all values once. |
| std::set<intptr_t> seen; |
| { |
| IdentityMap<void*, ZoneAllocationPolicy>::IteratableScope it_scope(&t.map); |
| for (auto it = it_scope.begin(); it != it_scope.end(); ++it) { |
| CHECK(seen.find(reinterpret_cast<intptr_t>(**it)) == seen.end()); |
| seen.insert(reinterpret_cast<intptr_t>(**it)); |
| } |
| } |
| for (intptr_t i = 0; i < 15; i++) { |
| CHECK(seen.find(i) != seen.end()); |
| } |
| } |
| |
| TEST(Iterator_smi_num_gc) { |
| const int kShift = 16039; |
| IdentityMapTester t; |
| int smi_keys[] = {1, 2, 7, 15, 23}; |
| Handle<Object> num_keys[] = {t.num(1.1), t.num(2.2), t.num(3.3), t.num(4.4), |
| t.num(5.5), t.num(6.6), t.num(7.7), t.num(8.8), |
| t.num(9.9), t.num(10.1)}; |
| // Initialize the map. |
| for (size_t i = 0; i < arraysize(smi_keys); i++) { |
| t.map.Insert(t.smi(smi_keys[i]), reinterpret_cast<void*>(i)); |
| } |
| for (size_t i = 0; i < arraysize(num_keys); i++) { |
| t.map.Insert(num_keys[i], reinterpret_cast<void*>(i + 5)); |
| } |
| |
| // Simulate GC by moving the SMIs. |
| t.SimulateGCByIncrementingSmisBy(kShift); |
| |
| // Check iterator sees all values. |
| std::set<intptr_t> seen; |
| { |
| IdentityMap<void*, ZoneAllocationPolicy>::IteratableScope it_scope(&t.map); |
| for (auto it = it_scope.begin(); it != it_scope.end(); ++it) { |
| CHECK(seen.find(reinterpret_cast<intptr_t>(**it)) == seen.end()); |
| seen.insert(reinterpret_cast<intptr_t>(**it)); |
| } |
| } |
| for (intptr_t i = 0; i < 15; i++) { |
| CHECK(seen.find(i) != seen.end()); |
| } |
| } |
| |
| void IterateCollisionTest(int stride) { |
| for (int load = 15; load <= 120; load = load * 2) { |
| IdentityMapTester t; |
| |
| { // Add entries to the map. |
| HandleScope scope(t.isolate()); |
| int next = 1; |
| for (int i = 0; i < load; i++) { |
| t.map.Insert(t.smi(next), reinterpret_cast<void*>(next)); |
| t.CheckFind(t.smi(next), reinterpret_cast<void*>(next)); |
| next = next + stride; |
| } |
| } |
| // Iterate through the map and check we see all elements only once. |
| std::set<intptr_t> seen; |
| { |
| IdentityMap<void*, ZoneAllocationPolicy>::IteratableScope it_scope( |
| &t.map); |
| for (auto it = it_scope.begin(); it != it_scope.end(); ++it) { |
| CHECK(seen.find(reinterpret_cast<intptr_t>(**it)) == seen.end()); |
| seen.insert(reinterpret_cast<intptr_t>(**it)); |
| } |
| } |
| // Check get and find on map. |
| { |
| HandleScope scope(t.isolate()); |
| int next = 1; |
| for (int i = 0; i < load; i++) { |
| CHECK(seen.find(next) != seen.end()); |
| t.CheckFind(t.smi(next), reinterpret_cast<void*>(next)); |
| t.CheckFindOrInsert(t.smi(next), reinterpret_cast<void*>(next)); |
| next = next + stride; |
| } |
| } |
| } |
| } |
| |
| TEST(IterateCollisions_1) { IterateCollisionTest(1); } |
| TEST(IterateCollisions_2) { IterateCollisionTest(2); } |
| TEST(IterateCollisions_3) { IterateCollisionTest(3); } |
| TEST(IterateCollisions_5) { IterateCollisionTest(5); } |
| TEST(IterateCollisions_7) { IterateCollisionTest(7); } |
| |
| void CollisionTest(int stride, bool rehash = false, bool resize = false) { |
| for (int load = 15; load <= 120; load = load * 2) { |
| IdentityMapTester t; |
| |
| { // Add entries to the map. |
| HandleScope scope(t.isolate()); |
| int next = 1; |
| for (int i = 0; i < load; i++) { |
| t.map.Insert(t.smi(next), reinterpret_cast<void*>(next)); |
| t.CheckFind(t.smi(next), reinterpret_cast<void*>(next)); |
| next = next + stride; |
| } |
| } |
| if (resize) t.Resize(); // Explicit resize (internal method). |
| if (rehash) t.Rehash(); // Explicit rehash (internal method). |
| { // Check find and get. |
| HandleScope scope(t.isolate()); |
| int next = 1; |
| for (int i = 0; i < load; i++) { |
| t.CheckFind(t.smi(next), reinterpret_cast<void*>(next)); |
| t.CheckFindOrInsert(t.smi(next), reinterpret_cast<void*>(next)); |
| next = next + stride; |
| } |
| } |
| } |
| } |
| |
| TEST(Collisions_1) { CollisionTest(1); } |
| TEST(Collisions_2) { CollisionTest(2); } |
| TEST(Collisions_3) { CollisionTest(3); } |
| TEST(Collisions_5) { CollisionTest(5); } |
| TEST(Collisions_7) { CollisionTest(7); } |
| TEST(Resize) { CollisionTest(9, false, true); } |
| TEST(Rehash) { CollisionTest(11, true, false); } |
| |
| TEST(ExplicitGC) { |
| IdentityMapTester t; |
| Handle<Object> num_keys[] = {t.num(2.1), t.num(2.4), t.num(3.3), t.num(4.3), |
| t.num(7.5), t.num(6.4), t.num(7.3), t.num(8.3), |
| t.num(8.9), t.num(10.4)}; |
| |
| // Insert some objects that should be in new space. |
| for (size_t i = 0; i < arraysize(num_keys); i++) { |
| t.map.Insert(num_keys[i], &num_keys[i]); |
| } |
| |
| // Do an explicit, real GC. |
| t.heap()->CollectGarbage(i::NEW_SPACE, i::GarbageCollectionReason::kTesting); |
| |
| // Check that searching for the numbers finds the same values. |
| for (size_t i = 0; i < arraysize(num_keys); i++) { |
| t.CheckFind(num_keys[i], &num_keys[i]); |
| t.CheckFindOrInsert(num_keys[i], &num_keys[i]); |
| } |
| } |
| |
| TEST(CanonicalHandleScope) { |
| Isolate* isolate = CcTest::i_isolate(); |
| Heap* heap = CcTest::heap(); |
| HandleScope outer(isolate); |
| CanonicalHandleScope outer_canonical(isolate); |
| |
| // Deduplicate smi handles. |
| std::vector<Handle<Object>> smi_handles; |
| for (int i = 0; i < 100; i++) { |
| smi_handles.push_back(Handle<Object>(Smi::FromInt(i), isolate)); |
| } |
| Address* next_handle = isolate->handle_scope_data()->next; |
| for (int i = 0; i < 100; i++) { |
| Handle<Object> new_smi = Handle<Object>(Smi::FromInt(i), isolate); |
| Handle<Object> old_smi = smi_handles[i]; |
| CHECK_EQ(new_smi.location(), old_smi.location()); |
| } |
| // Check that no new handles have been allocated. |
| CHECK_EQ(next_handle, isolate->handle_scope_data()->next); |
| |
| // Deduplicate root list items. |
| Handle<String> empty_string(ReadOnlyRoots(heap).empty_string(), isolate); |
| Handle<Map> free_space_map(ReadOnlyRoots(heap).free_space_map(), isolate); |
| Handle<Symbol> uninitialized_symbol( |
| ReadOnlyRoots(heap).uninitialized_symbol(), isolate); |
| CHECK_EQ(isolate->factory()->empty_string().location(), |
| empty_string.location()); |
| CHECK_EQ(isolate->factory()->free_space_map().location(), |
| free_space_map.location()); |
| CHECK_EQ(isolate->factory()->uninitialized_symbol().location(), |
| uninitialized_symbol.location()); |
| // Check that no new handles have been allocated. |
| CHECK_EQ(next_handle, isolate->handle_scope_data()->next); |
| |
| // Test ordinary heap objects. |
| Handle<HeapNumber> number1 = isolate->factory()->NewHeapNumber(3.3); |
| Handle<String> string1 = |
| isolate->factory()->NewStringFromAsciiChecked("test"); |
| next_handle = isolate->handle_scope_data()->next; |
| Handle<HeapNumber> number2(*number1, isolate); |
| Handle<String> string2(*string1, isolate); |
| CHECK_EQ(number1.location(), number2.location()); |
| CHECK_EQ(string1.location(), string2.location()); |
| CcTest::CollectAllGarbage(); |
| Handle<HeapNumber> number3(*number2, isolate); |
| Handle<String> string3(*string2, isolate); |
| CHECK_EQ(number1.location(), number3.location()); |
| CHECK_EQ(string1.location(), string3.location()); |
| // Check that no new handles have been allocated. |
| CHECK_EQ(next_handle, isolate->handle_scope_data()->next); |
| |
| // Inner handle scope do not create canonical handles. |
| { |
| HandleScope inner(isolate); |
| Handle<HeapNumber> number4(*number1, isolate); |
| Handle<String> string4(*string1, isolate); |
| CHECK_NE(number1.location(), number4.location()); |
| CHECK_NE(string1.location(), string4.location()); |
| |
| // Nested canonical scope does not conflict with outer canonical scope, |
| // but does not canonicalize across scopes. |
| CanonicalHandleScope inner_canonical(isolate); |
| Handle<HeapNumber> number5(*number4, isolate); |
| Handle<String> string5(*string4, isolate); |
| CHECK_NE(number4.location(), number5.location()); |
| CHECK_NE(string4.location(), string5.location()); |
| CHECK_NE(number1.location(), number5.location()); |
| CHECK_NE(string1.location(), string5.location()); |
| |
| Handle<HeapNumber> number6(*number1, isolate); |
| Handle<String> string6(*string1, isolate); |
| CHECK_NE(number4.location(), number6.location()); |
| CHECK_NE(string4.location(), string6.location()); |
| CHECK_NE(number1.location(), number6.location()); |
| CHECK_NE(string1.location(), string6.location()); |
| CHECK_EQ(number5.location(), number6.location()); |
| CHECK_EQ(string5.location(), string6.location()); |
| } |
| } |
| |
| TEST(GCShortCutting) { |
| if (FLAG_single_generation) return; |
| ManualGCScope manual_gc_scope; |
| IdentityMapTester t; |
| Isolate* isolate = CcTest::i_isolate(); |
| Factory* factory = isolate->factory(); |
| const int kDummyValue = 0; |
| |
| for (int i = 0; i < 16; i++) { |
| // Insert a varying number of Smis as padding to ensure some tests straddle |
| // a boundary where the thin string short cutting will cause size_ to be |
| // greater to capacity_ if not corrected by IdentityMap |
| // (see crbug.com/704132). |
| for (int j = 0; j < i; j++) { |
| t.map.Insert(t.smi(j), reinterpret_cast<void*>(kDummyValue)); |
| } |
| |
| Handle<String> thin_string = |
| factory->NewStringFromAsciiChecked("thin_string"); |
| Handle<String> internalized_string = |
| factory->InternalizeString(thin_string); |
| DCHECK_IMPLIES(FLAG_thin_strings, thin_string->IsThinString()); |
| DCHECK_NE(*thin_string, *internalized_string); |
| |
| // Insert both keys into the map. |
| t.map.Insert(thin_string, &thin_string); |
| t.map.Insert(internalized_string, &internalized_string); |
| |
| // Do an explicit, real GC, this should short-cut the thin string to point |
| // to the internalized string. |
| t.heap()->CollectGarbage(i::NEW_SPACE, |
| i::GarbageCollectionReason::kTesting); |
| DCHECK_IMPLIES(FLAG_thin_strings && !FLAG_optimize_for_size, |
| *thin_string == *internalized_string); |
| |
| // Check that getting the object points to one of the handles. |
| void** thin_string_entry = t.map.Find(thin_string); |
| CHECK(*thin_string_entry == &thin_string || |
| *thin_string_entry == &internalized_string); |
| void** internalized_string_entry = t.map.Find(internalized_string); |
| CHECK(*internalized_string_entry == &thin_string || |
| *internalized_string_entry == &internalized_string); |
| |
| // Trigger resize. |
| for (int j = 0; j < 16; j++) { |
| t.map.Insert(t.smi(j + 16), reinterpret_cast<void*>(kDummyValue)); |
| } |
| t.map.Clear(); |
| } |
| } |
| |
| } // namespace internal |
| } // namespace v8 |