2015-05-06 12:40:21 +00:00
|
|
|
// 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.
|
|
|
|
|
2017-02-20 21:46:38 +00:00
|
|
|
#include <set>
|
|
|
|
|
2019-05-22 07:55:37 +00:00
|
|
|
#include "src/execution/isolate.h"
|
2018-04-09 19:11:22 +00:00
|
|
|
#include "src/heap/factory-inl.h"
|
2018-12-17 17:01:48 +00:00
|
|
|
#include "src/objects/heap-number-inl.h"
|
2019-05-23 13:27:57 +00:00
|
|
|
#include "src/utils/identity-map.h"
|
2019-05-23 08:51:46 +00:00
|
|
|
#include "src/objects/objects.h"
|
2016-09-20 16:07:25 +00:00
|
|
|
#include "src/zone/zone.h"
|
2015-05-06 12:40:21 +00:00
|
|
|
#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:
|
2017-02-20 21:46:38 +00:00
|
|
|
IdentityMap<void*, ZoneAllocationPolicy> map;
|
2015-05-06 12:40:21 +00:00
|
|
|
|
2017-02-20 21:46:38 +00:00
|
|
|
IdentityMapTester() : map(heap(), ZoneAllocationPolicy(main_zone())) {}
|
2015-05-06 12:40:21 +00:00
|
|
|
|
|
|
|
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);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-02-20 21:46:38 +00:00
|
|
|
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}
|
2018-04-19 14:10:45 +00:00
|
|
|
void* deleted_entry_1;
|
|
|
|
CHECK(map.Delete(key1, &deleted_entry_1));
|
2017-02-20 21:46:38 +00:00
|
|
|
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}
|
2018-04-19 14:10:45 +00:00
|
|
|
void* deleted_entry_2;
|
|
|
|
CHECK(map.Delete(key2, &deleted_entry_2));
|
2017-02-20 21:46:38 +00:00
|
|
|
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);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-05-06 12:40:21 +00:00
|
|
|
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) {
|
2017-02-20 21:46:38 +00:00
|
|
|
for (int i = 0; i < map.capacity_; i++) {
|
2018-10-18 23:44:29 +00:00
|
|
|
Address key = map.keys_[i];
|
|
|
|
if (!Internals::HasHeapObjectTag(key)) {
|
|
|
|
map.keys_[i] = Internals::IntToSmi(Internals::SmiValue(key) + shift);
|
2015-05-06 12:40:21 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
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);
|
|
|
|
}
|
|
|
|
|
2017-02-20 21:46:38 +00:00
|
|
|
void CheckDelete(Handle<Object> key, void* value) {
|
2018-04-19 14:10:45 +00:00
|
|
|
void* entry;
|
|
|
|
CHECK(map.Delete(key, &entry));
|
2017-02-20 21:46:38 +00:00
|
|
|
CHECK_NOT_NULL(entry);
|
|
|
|
CHECK_EQ(value, entry);
|
|
|
|
}
|
|
|
|
|
2015-05-06 12:40:21 +00:00
|
|
|
void PrintMap() {
|
|
|
|
PrintF("{\n");
|
2017-02-20 21:46:38 +00:00
|
|
|
for (int i = 0; i < map.capacity_; i++) {
|
2015-05-06 12:40:21 +00:00
|
|
|
PrintF(" %3d: %p => %p\n", i, reinterpret_cast<void*>(map.keys_[i]),
|
|
|
|
reinterpret_cast<void*>(map.values_[i]));
|
|
|
|
}
|
|
|
|
PrintF("}\n");
|
|
|
|
}
|
|
|
|
|
2017-02-20 21:46:38 +00:00
|
|
|
void Resize() { map.Resize(map.capacity_ * 4); }
|
2015-05-06 12:40:21 +00:00
|
|
|
|
|
|
|
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)));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-02-20 21:46:38 +00:00
|
|
|
TEST(Delete_smi_not_found) {
|
|
|
|
IdentityMapTester t;
|
|
|
|
for (int i = 0; i < 100; i++) {
|
2018-04-19 14:10:45 +00:00
|
|
|
void* deleted_value = &t;
|
|
|
|
CHECK(!t.map.Delete(t.smi(i), &deleted_value));
|
|
|
|
CHECK_EQ(&t, deleted_value);
|
2017-02-20 21:46:38 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
TEST(Delete_num_not_found) {
|
|
|
|
IdentityMapTester t;
|
|
|
|
for (int i = 0; i < 100; i++) {
|
2018-04-19 14:10:45 +00:00
|
|
|
void* deleted_value = &t;
|
|
|
|
CHECK(!t.map.Delete(t.num(i + 0.2), &deleted_value));
|
|
|
|
CHECK_EQ(&t, deleted_value);
|
2017-02-20 21:46:38 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
TEST(GetFind_smi_0) {
|
|
|
|
IdentityMapTester t;
|
|
|
|
t.TestGetFind(t.smi(0), t.isolate(), t.smi(1), t.heap());
|
|
|
|
}
|
2015-05-06 12:40:21 +00:00
|
|
|
|
|
|
|
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());
|
|
|
|
}
|
|
|
|
|
2017-02-20 21:46:38 +00:00
|
|
|
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());
|
|
|
|
}
|
2015-05-06 12:40:21 +00:00
|
|
|
|
|
|
|
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);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-02-20 21:46:38 +00:00
|
|
|
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);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2015-05-06 12:40:21 +00:00
|
|
|
|
|
|
|
TEST(GetFind_num_1000) {
|
|
|
|
const int kPrime = 137;
|
|
|
|
IdentityMapTester t;
|
|
|
|
int val1;
|
|
|
|
int val2;
|
|
|
|
|
2015-10-22 12:12:12 +00:00
|
|
|
for (int i = 0; i < 1000; i++) {
|
2015-05-06 12:40:21 +00:00
|
|
|
t.TestGetFind(t.smi(i * kPrime), &val1, t.smi(i * kPrime + 1), &val2);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-02-20 21:46:38 +00:00
|
|
|
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) {
|
2018-04-19 14:10:45 +00:00
|
|
|
void* entry;
|
|
|
|
CHECK(t.map.Delete(t.smi(i * kPrime), &entry));
|
2017-02-20 21:46:38 +00:00
|
|
|
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) {
|
2018-04-19 14:10:45 +00:00
|
|
|
void* entry;
|
|
|
|
CHECK(t.map.Delete(t.smi(i * kPrime), &entry));
|
2017-02-20 21:46:38 +00:00
|
|
|
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);
|
|
|
|
}
|
|
|
|
}
|
2015-05-06 12:40:21 +00:00
|
|
|
|
|
|
|
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);
|
|
|
|
}
|
|
|
|
|
2017-02-20 21:46:38 +00:00
|
|
|
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);
|
|
|
|
}
|
2015-05-06 12:40:21 +00:00
|
|
|
|
|
|
|
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);
|
|
|
|
}
|
|
|
|
|
2017-02-20 21:46:38 +00:00
|
|
|
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);
|
|
|
|
}
|
2015-05-06 12:40:21 +00:00
|
|
|
|
|
|
|
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]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-02-20 21:46:38 +00:00
|
|
|
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]);
|
|
|
|
}
|
|
|
|
}
|
2015-05-06 12:40:21 +00:00
|
|
|
|
|
|
|
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]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-02-20 21:46:38 +00:00
|
|
|
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));
|
|
|
|
}
|
|
|
|
|
2017-03-03 17:14:40 +00:00
|
|
|
// Check iterator sees all values once.
|
2017-02-20 21:46:38 +00:00
|
|
|
std::set<intptr_t> seen;
|
|
|
|
{
|
|
|
|
IdentityMap<void*, ZoneAllocationPolicy>::IteratableScope it_scope(&t.map);
|
|
|
|
for (auto it = it_scope.begin(); it != it_scope.end(); ++it) {
|
2017-03-03 17:14:40 +00:00
|
|
|
CHECK(seen.find(reinterpret_cast<intptr_t>(**it)) == seen.end());
|
2017-02-20 21:46:38 +00:00
|
|
|
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) {
|
2017-03-03 17:14:40 +00:00
|
|
|
CHECK(seen.find(reinterpret_cast<intptr_t>(**it)) == seen.end());
|
2017-02-20 21:46:38 +00:00
|
|
|
seen.insert(reinterpret_cast<intptr_t>(**it));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
for (intptr_t i = 0; i < 15; i++) {
|
|
|
|
CHECK(seen.find(i) != seen.end());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-03-03 17:14:40 +00:00
|
|
|
void IterateCollisionTest(int stride) {
|
|
|
|
for (int load = 15; load <= 120; load = load * 2) {
|
|
|
|
IdentityMapTester t;
|
2017-02-20 21:46:38 +00:00
|
|
|
|
2017-03-03 17:14:40 +00:00
|
|
|
{ // 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;
|
2017-02-20 21:46:38 +00:00
|
|
|
}
|
|
|
|
}
|
2017-03-03 17:14:40 +00:00
|
|
|
// 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;
|
|
|
|
}
|
2017-02-20 21:46:38 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2015-05-06 12:40:21 +00:00
|
|
|
|
2017-03-03 17:14:40 +00:00
|
|
|
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); }
|
|
|
|
|
2015-05-06 12:40:21 +00:00
|
|
|
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.
|
2016-09-07 10:02:58 +00:00
|
|
|
t.heap()->CollectGarbage(i::NEW_SPACE, i::GarbageCollectionReason::kTesting);
|
2015-05-06 12:40:21 +00:00
|
|
|
|
|
|
|
// 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]);
|
|
|
|
}
|
|
|
|
}
|
2015-09-30 13:46:56 +00:00
|
|
|
|
2015-10-26 15:33:02 +00:00
|
|
|
TEST(CanonicalHandleScope) {
|
|
|
|
Isolate* isolate = CcTest::i_isolate();
|
|
|
|
Heap* heap = CcTest::heap();
|
|
|
|
HandleScope outer(isolate);
|
|
|
|
CanonicalHandleScope outer_canonical(isolate);
|
|
|
|
|
|
|
|
// Deduplicate smi handles.
|
2017-08-29 12:40:31 +00:00
|
|
|
std::vector<Handle<Object>> smi_handles;
|
2015-10-26 15:33:02 +00:00
|
|
|
for (int i = 0; i < 100; i++) {
|
2017-08-29 12:40:31 +00:00
|
|
|
smi_handles.push_back(Handle<Object>(Smi::FromInt(i), isolate));
|
2015-10-26 15:33:02 +00:00
|
|
|
}
|
2018-10-31 22:52:56 +00:00
|
|
|
Address* next_handle = isolate->handle_scope_data()->next;
|
2015-10-26 15:33:02 +00:00
|
|
|
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.
|
2018-07-04 09:10:05 +00:00
|
|
|
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);
|
2015-10-26 15:33:02 +00:00
|
|
|
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;
|
2018-06-23 09:05:50 +00:00
|
|
|
Handle<HeapNumber> number2(*number1, isolate);
|
|
|
|
Handle<String> string2(*string1, isolate);
|
2015-10-26 15:33:02 +00:00
|
|
|
CHECK_EQ(number1.location(), number2.location());
|
|
|
|
CHECK_EQ(string1.location(), string2.location());
|
2017-04-26 22:16:41 +00:00
|
|
|
CcTest::CollectAllGarbage();
|
2018-06-23 09:05:50 +00:00
|
|
|
Handle<HeapNumber> number3(*number2, isolate);
|
|
|
|
Handle<String> string3(*string2, isolate);
|
2015-10-26 15:33:02 +00:00
|
|
|
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);
|
2018-06-23 09:05:50 +00:00
|
|
|
Handle<HeapNumber> number4(*number1, isolate);
|
|
|
|
Handle<String> string4(*string1, isolate);
|
2015-10-26 15:33:02 +00:00
|
|
|
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);
|
2018-06-23 09:05:50 +00:00
|
|
|
Handle<HeapNumber> number5(*number4, isolate);
|
|
|
|
Handle<String> string5(*string4, isolate);
|
2015-10-26 15:33:02 +00:00
|
|
|
CHECK_NE(number4.location(), number5.location());
|
|
|
|
CHECK_NE(string4.location(), string5.location());
|
|
|
|
CHECK_NE(number1.location(), number5.location());
|
|
|
|
CHECK_NE(string1.location(), string5.location());
|
|
|
|
|
2018-06-23 09:05:50 +00:00
|
|
|
Handle<HeapNumber> number6(*number1, isolate);
|
|
|
|
Handle<String> string6(*string1, isolate);
|
2015-10-26 15:33:02 +00:00
|
|
|
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());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-06-20 21:36:26 +00:00
|
|
|
TEST(GCShortCutting) {
|
2017-10-05 11:57:49 +00:00
|
|
|
ManualGCScope manual_gc_scope;
|
2017-06-20 21:36:26 +00:00
|
|
|
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.Set(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.Set(thin_string, &thin_string);
|
|
|
|
t.map.Set(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.Get(thin_string);
|
|
|
|
CHECK(*thin_string_entry == &thin_string ||
|
|
|
|
*thin_string_entry == &internalized_string);
|
|
|
|
void** internalized_string_entry = t.map.Get(internalized_string);
|
|
|
|
CHECK(*internalized_string_entry == &thin_string ||
|
|
|
|
*internalized_string_entry == &internalized_string);
|
|
|
|
|
|
|
|
// Trigger resize.
|
|
|
|
for (int j = 0; j < 16; j++) {
|
|
|
|
t.map.Set(t.smi(j + 16), reinterpret_cast<void*>(kDummyValue));
|
|
|
|
}
|
|
|
|
t.map.Clear();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-09-30 13:46:56 +00:00
|
|
|
} // namespace internal
|
|
|
|
} // namespace v8
|