// 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 "src/v8.h" #include "src/identity-map.h" #include "src/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*> map; IdentityMapTester() : map(heap(), main_zone()) {} Heap* heap() { return isolate()->heap(); } Isolate* isolate() { return main_isolate(); } void TestGetFind(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. void** entry = map.Get(key1); CHECK_NOT_NULL(entry); *entry = val1; for (int i = 0; i < 3; i++) { // Get and find {key1} K times. { void** nentry = map.Get(key1); CHECK_EQ(entry, nentry); CHECK_EQ(val1, *nentry); CHECK_NULL(map.Find(key2)); } { void** nentry = map.Find(key1); CHECK_EQ(entry, nentry); CHECK_EQ(val1, *nentry); CHECK_NULL(map.Find(key2)); } } // Set {key2} the first time. void** entry2 = map.Get(key2); CHECK_NOT_NULL(entry2); *entry2 = val2; for (int i = 0; i < 3; i++) { // Get and find {key1} and {key2} K times. { void** nentry = map.Get(key2); CHECK_EQ(entry2, nentry); CHECK_EQ(val2, *nentry); } { void** nentry = map.Find(key2); CHECK_EQ(entry2, nentry); CHECK_EQ(val2, *nentry); } { void** nentry = map.Find(key1); CHECK_EQ(val1, *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.size_; i++) { if (map.keys_[i]->IsSmi()) { map.keys_[i] = Smi::FromInt(Smi::cast(map.keys_[i])->value() + 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 CheckGet(Handle<Object> key, void* value) { void** entry = map.Get(key); CHECK_NOT_NULL(entry); CHECK_EQ(value, *entry); } void PrintMap() { PrintF("{\n"); for (int i = 0; i < map.size_; 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(); } 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(GetFind_smi_13) { IdentityMapTester t; t.TestGetFind(t.smi(13), t.isolate(), t.smi(17), t.heap()); } TEST(GetFind_num_13) { IdentityMapTester t; t.TestGetFind(t.num(13.1), t.isolate(), t.num(17.1), t.heap()); } TEST(GetFind_smi_17m) { const int kInterval = 17; const int kShift = 1099; IdentityMapTester t; for (int i = 1; i < 100; i += kInterval) { t.map.Set(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.CheckGet(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(GetFind_num_1000) { const int kPrime = 137; IdentityMapTester t; int val1; int val2; for (int i = 0; i < 1000; i++) { t.TestGetFind(t.smi(i * kPrime), &val1, t.smi(i * kPrime + 1), &val2); } } TEST(GetFind_smi_gc) { const int kKey = 33; const int kShift = 1211; IdentityMapTester t; t.map.Set(t.smi(kKey), &t); t.SimulateGCByIncrementingSmisBy(kShift); t.CheckFind(t.smi(kKey + kShift), &t); t.CheckGet(t.smi(kKey + kShift), &t); } TEST(GetFind_smi_gc2) { int kKey1 = 1; int kKey2 = 33; const int kShift = 1211; IdentityMapTester t; t.map.Set(t.smi(kKey1), &kKey1); t.map.Set(t.smi(kKey2), &kKey2); t.SimulateGCByIncrementingSmisBy(kShift); t.CheckFind(t.smi(kKey1 + kShift), &kKey1); t.CheckGet(t.smi(kKey1 + kShift), &kKey1); t.CheckFind(t.smi(kKey2 + kShift), &kKey2); t.CheckGet(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.TestGetFind(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.CheckGet(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.Set(t.smi(smi_keys[i]), &smi_keys[i]); } for (size_t i = 0; i < arraysize(num_keys); i++) { t.map.Set(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.CheckGet(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.CheckGet(num_keys[i], &num_keys[i]); } } 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.Set(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.CheckGet(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.Set(num_keys[i], &num_keys[i]); } // Do an explicit, real GC. t.heap()->CollectGarbage(i::NEW_SPACE); // 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.CheckGet(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. List<Handle<Object> > smi_handles; for (int i = 0; i < 100; i++) { smi_handles.Add(Handle<Object>(Smi::FromInt(i), isolate)); } Object** 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(heap->empty_string()); Handle<Map> free_space_map(heap->free_space_map()); Handle<Symbol> uninitialized_symbol(heap->uninitialized_symbol()); 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); Handle<String> string2(*string1); CHECK_EQ(number1.location(), number2.location()); CHECK_EQ(string1.location(), string2.location()); heap->CollectAllGarbage(); Handle<HeapNumber> number3(*number2); Handle<String> string3(*string2); 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); Handle<String> string4(*string1); 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); Handle<String> string5(*string4); 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); Handle<String> string6(*string1); 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()); } } } // namespace internal } // namespace v8