e6d2edd710
Add flag --always-use-string-forwarding-table to always use the forwarding table (usually only used for shared strings) instead of ThinString migrations initially (during GC strings will be migrated to normal ThinStrings). The goal is to get more coverage of this code that is designed for shared strings. Bug: v8:12007 Change-Id: I7eb2e5ccf0018c4ac349611aebe337d8288de5c8 Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/3536650 Reviewed-by: Dominik Inführ <dinfuehr@chromium.org> Reviewed-by: Shu-yu Guo <syg@chromium.org> Commit-Queue: Patrick Thier <pthier@chromium.org> Cr-Commit-Position: refs/heads/main@{#80206}
841 lines
26 KiB
C++
841 lines
26 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/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;
|
|
// We don't create ThinStrings immediately when using the forwarding table.
|
|
if (FLAG_always_use_string_forwarding_table) 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(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_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
|