v8/test/cctest/test-identity-map.cc
Ross McIlroy dba45fde81 [IdentityMap] Remove ability to delete entries during iteration.
Deletion can cause collisions to be moved in the map which breaks iteration.
For now just remove this support. Also add some additional collision tests
to the test.

BUG=v8:5203

Change-Id: I54a0a4af51da08b3f963dc1d7661dba291e4efea
Reviewed-on: https://chromium-review.googlesource.com/445900
Reviewed-by: Ben Titzer <titzer@chromium.org>
Commit-Queue: Ross McIlroy <rmcilroy@chromium.org>
Cr-Commit-Position: refs/heads/master@{#43621}
2017-03-06 21:15:20 +00:00

780 lines
23 KiB
C++

// 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/factory.h"
#include "src/identity-map.h"
#include "src/isolate.h"
#include "src/objects.h"
#include "src/zone/zone.h"
// FIXME(mstarzinger, marja): This is weird, but required because of the missing
// (disallowed) include: src/factory.h -> src/objects-inl.h
#include "src/objects-inl.h"
// FIXME(mstarzinger, marja): This is weird, but required because of the missing
// (disallowed) include: src/feedback-vector.h ->
// src/feedback-vector-inl.h
#include "src/feedback-vector-inl.h"
#include "src/v8.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 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);
}
}
}
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.
void** entry1 = map.Get(key1);
CHECK_NOT_NULL(entry1);
*entry1 = val1;
void** entry2 = map.Get(key2);
CHECK_NOT_NULL(entry2);
*entry2 = 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 = map.Delete(key1);
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 = map.Delete(key2);
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++) {
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 CheckDelete(Handle<Object> key, void* value) {
void* entry = map.Delete(key);
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++) {
CHECK_NULL(t.map.Delete(t.smi(i)));
}
}
TEST(Delete_num_not_found) {
IdentityMapTester t;
for (int i = 0; i < 100; i++) {
CHECK_NULL(t.map.Delete(t.num(i + 0.2)));
}
}
TEST(GetFind_smi_0) {
IdentityMapTester t;
t.TestGetFind(t.smi(0), t.isolate(), t.smi(1), t.heap());
}
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(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.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(Delete_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.CheckDelete(t.smi(i), reinterpret_cast<void*>(i + kShift));
for (int j = 1; j < 100; j += kInterval) {
void** 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.TestGetFind(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.Set(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 = t.map.Delete(t.smi(i * kPrime));
CHECK_EQ(reinterpret_cast<void*>(i * kPrime), entry);
}
for (int i = 0; i < 1000; i++) {
void** 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 = t.map.Delete(t.smi(i * kPrime));
CHECK_EQ(reinterpret_cast<void*>(i * kPrime), entry);
}
for (int i = 0; i < 1000; i++) {
void** 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.Set(t.smi(kKey), &t);
t.SimulateGCByIncrementingSmisBy(kShift);
t.CheckFind(t.smi(kKey + kShift), &t);
t.CheckGet(t.smi(kKey + kShift), &t);
}
TEST(Delete_smi_gc) {
const int kKey = 33;
const int kShift = 1211;
IdentityMapTester t;
t.map.Set(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.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(Delete_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.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.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(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.Set(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.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]);
}
}
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.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]);
}
// 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.Set(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.Set(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.Set(t.smi(smi_keys[i]), reinterpret_cast<void*>(i));
}
for (size_t i = 0; i < arraysize(num_keys); i++) {
t.map.Set(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.Set(t.smi(smi_keys[i]), reinterpret_cast<void*>(i));
}
for (size_t i = 0; i < arraysize(num_keys); i++) {
t.map.Set(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.Set(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.CheckGet(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.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, 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.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());
CcTest::CollectAllGarbage(i::Heap::kFinalizeIncrementalMarkingMask);
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