test-weakmaps.cc 13.1 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
// Copyright 2011 the V8 project authors. All rights reserved.
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
//     * Redistributions of source code must retain the above copyright
//       notice, this list of conditions and the following disclaimer.
//     * Redistributions in binary form must reproduce the above
//       copyright notice, this list of conditions and the following
//       disclaimer in the documentation and/or other materials provided
//       with the distribution.
//     * Neither the name of Google Inc. nor the names of its
//       contributors may be used to endorse or promote products derived
//       from this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

28 29
#include <utility>

30
#include "src/global-handles.h"
31
#include "src/heap/factory.h"
32
#include "src/heap/heap-inl.h"
33 34
#include "src/isolate.h"
#include "src/objects-inl.h"
35
#include "src/objects/hash-table-inl.h"
36
#include "src/objects/js-collection-inl.h"
37
#include "test/cctest/cctest.h"
38
#include "test/cctest/heap/heap-utils.h"
39

40 41
namespace v8 {
namespace internal {
42
namespace test_weakmaps {
43

44 45 46 47
static Isolate* GetIsolateFrom(LocalContext* context) {
  return reinterpret_cast<Isolate*>((*context)->GetIsolate());
}

48
static int NumberOfWeakCalls = 0;
49
static void WeakPointerCallback(const v8::WeakCallbackInfo<void>& data) {
50 51 52
  std::pair<v8::Persistent<v8::Value>*, int>* p =
      reinterpret_cast<std::pair<v8::Persistent<v8::Value>*, int>*>(
          data.GetParameter());
53
  CHECK_EQ(1234, p->second);
54
  NumberOfWeakCalls++;
55
  p->first->Reset();
56 57 58 59
}


TEST(Weakness) {
60
  FLAG_incremental_marking = false;
61
  LocalContext context;
62 63
  Isolate* isolate = GetIsolateFrom(&context);
  Factory* factory = isolate->factory();
64
  HandleScope scope(isolate);
65
  Handle<JSWeakMap> weakmap = isolate->factory()->NewJSWeakMap();
66
  GlobalHandles* global_handles = isolate->global_handles();
67 68 69 70

  // Keep global reference to the key.
  Handle<Object> key;
  {
71
    HandleScope scope(isolate);
72 73
    Handle<Map> map = factory->NewMap(JS_OBJECT_TYPE, JSObject::kHeaderSize);
    Handle<JSObject> object = factory->NewJSObjectFromMap(map);
74 75 76 77
    key = global_handles->Create(*object);
  }
  CHECK(!global_handles->IsWeak(key.location()));

78
  // Put two chained entries into weak map.
79
  {
80
    HandleScope scope(isolate);
81 82
    Handle<Map> map = factory->NewMap(JS_OBJECT_TYPE, JSObject::kHeaderSize);
    Handle<JSObject> object = factory->NewJSObjectFromMap(map);
yurys's avatar
yurys committed
83
    Handle<Smi> smi(Smi::FromInt(23), isolate);
84
    int32_t hash = key->GetOrCreateHash(isolate)->value();
85
    JSWeakCollection::Set(weakmap, key, object, hash);
86
    int32_t object_hash = object->GetOrCreateHash(isolate)->value();
87
    JSWeakCollection::Set(weakmap, object, smi, object_hash);
88
  }
89
  CHECK_EQ(2, EphemeronHashTable::cast(weakmap->table())->NumberOfElements());
90 91

  // Force a full GC.
92
  CcTest::PreciseCollectAllGarbage();
93
  CHECK_EQ(0, NumberOfWeakCalls);
94
  CHECK_EQ(2, EphemeronHashTable::cast(weakmap->table())->NumberOfElements());
95
  CHECK_EQ(
96
      0, EphemeronHashTable::cast(weakmap->table())->NumberOfDeletedElements());
97 98

  // Make the global reference to the key weak.
99 100 101 102
  std::pair<Handle<Object>*, int> handle_and_id(&key, 1234);
  GlobalHandles::MakeWeak(
      key.location(), reinterpret_cast<void*>(&handle_and_id),
      &WeakPointerCallback, v8::WeakCallbackType::kParameter);
103 104
  CHECK(global_handles->IsWeak(key.location()));

105
  CcTest::PreciseCollectAllGarbage();
106
  CHECK_EQ(1, NumberOfWeakCalls);
107 108 109
  CHECK_EQ(0, EphemeronHashTable::cast(weakmap->table())->NumberOfElements());
  CHECK_EQ(
      2, EphemeronHashTable::cast(weakmap->table())->NumberOfDeletedElements());
110 111 112 113 114
}


TEST(Shrinking) {
  LocalContext context;
115 116
  Isolate* isolate = GetIsolateFrom(&context);
  Factory* factory = isolate->factory();
117
  HandleScope scope(isolate);
118
  Handle<JSWeakMap> weakmap = isolate->factory()->NewJSWeakMap();
119 120

  // Check initial capacity.
121
  CHECK_EQ(32, EphemeronHashTable::cast(weakmap->table())->Capacity());
122 123 124

  // Fill up weak map to trigger capacity change.
  {
125
    HandleScope scope(isolate);
126
    Handle<Map> map = factory->NewMap(JS_OBJECT_TYPE, JSObject::kHeaderSize);
127
    for (int i = 0; i < 32; i++) {
128
      Handle<JSObject> object = factory->NewJSObjectFromMap(map);
yurys's avatar
yurys committed
129
      Handle<Smi> smi(Smi::FromInt(i), isolate);
130
      int32_t object_hash = object->GetOrCreateHash(isolate)->value();
131
      JSWeakCollection::Set(weakmap, object, smi, object_hash);
132 133 134 135
    }
  }

  // Check increased capacity.
136
  CHECK_EQ(128, EphemeronHashTable::cast(weakmap->table())->Capacity());
137 138

  // Force a full GC.
139
  CHECK_EQ(32, EphemeronHashTable::cast(weakmap->table())->NumberOfElements());
140
  CHECK_EQ(
141
      0, EphemeronHashTable::cast(weakmap->table())->NumberOfDeletedElements());
142
  CcTest::PreciseCollectAllGarbage();
143
  CHECK_EQ(0, EphemeronHashTable::cast(weakmap->table())->NumberOfElements());
144
  CHECK_EQ(
145 146
      32,
      EphemeronHashTable::cast(weakmap->table())->NumberOfDeletedElements());
147 148

  // Check shrunk capacity.
149
  CHECK_EQ(32, EphemeronHashTable::cast(weakmap->table())->Capacity());
150
}
151

152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223
namespace {
bool EphemeronHashTableContainsKey(EphemeronHashTable table, HeapObject key) {
  for (int i = 0; i < table.Capacity(); ++i) {
    if (table->KeyAt(i) == key) return true;
  }
  return false;
}
}  // namespace

TEST(WeakMapPromotion) {
  LocalContext context;
  Isolate* isolate = GetIsolateFrom(&context);
  Factory* factory = isolate->factory();
  HandleScope scope(isolate);
  Handle<JSWeakMap> weakmap = isolate->factory()->NewJSWeakMap();

  CcTest::CollectAllGarbage();
  CHECK(ObjectInYoungGeneration(weakmap->table()));

  Handle<Map> map = factory->NewMap(JS_OBJECT_TYPE, JSObject::kHeaderSize);
  Handle<JSObject> object = factory->NewJSObjectFromMap(map);
  Handle<Smi> smi(Smi::FromInt(1), isolate);
  int32_t object_hash = object->GetOrCreateHash(isolate)->value();
  JSWeakCollection::Set(weakmap, object, smi, object_hash);

  CHECK(EphemeronHashTableContainsKey(
      EphemeronHashTable::cast(weakmap->table()), *object));
  CcTest::CollectAllGarbage();

  CHECK(ObjectInYoungGeneration(*object));
  CHECK(!ObjectInYoungGeneration(weakmap->table()));
  CHECK(EphemeronHashTableContainsKey(
      EphemeronHashTable::cast(weakmap->table()), *object));

  CcTest::CollectAllGarbage();
  CHECK(!ObjectInYoungGeneration(*object));
  CHECK(!ObjectInYoungGeneration(weakmap->table()));
  CHECK(EphemeronHashTableContainsKey(
      EphemeronHashTable::cast(weakmap->table()), *object));
}

TEST(WeakMapScavenge) {
  LocalContext context;
  Isolate* isolate = GetIsolateFrom(&context);
  Factory* factory = isolate->factory();
  HandleScope scope(isolate);
  Handle<JSWeakMap> weakmap = isolate->factory()->NewJSWeakMap();

  CcTest::CollectAllGarbage();
  CHECK(ObjectInYoungGeneration(weakmap->table()));

  Handle<Map> map = factory->NewMap(JS_OBJECT_TYPE, JSObject::kHeaderSize);
  Handle<JSObject> object = factory->NewJSObjectFromMap(map);
  Handle<Smi> smi(Smi::FromInt(1), isolate);
  int32_t object_hash = object->GetOrCreateHash(isolate)->value();
  JSWeakCollection::Set(weakmap, object, smi, object_hash);

  CHECK(EphemeronHashTableContainsKey(
      EphemeronHashTable::cast(weakmap->table()), *object));

  heap::GcAndSweep(isolate->heap(), NEW_SPACE);
  CHECK(ObjectInYoungGeneration(*object));
  CHECK(!ObjectInYoungGeneration(weakmap->table()));
  CHECK(EphemeronHashTableContainsKey(
      EphemeronHashTable::cast(weakmap->table()), *object));

  heap::GcAndSweep(isolate->heap(), NEW_SPACE);
  CHECK(!ObjectInYoungGeneration(*object));
  CHECK(!ObjectInYoungGeneration(weakmap->table()));
  CHECK(EphemeronHashTableContainsKey(
      EphemeronHashTable::cast(weakmap->table()), *object));
}
224 225 226

// Test that weak map values on an evacuation candidate which are not reachable
// by other paths are correctly recorded in the slots buffer.
227
TEST(Regress2060a) {
228
  if (i::FLAG_never_compact) return;
229 230
  FLAG_always_compact = true;
  LocalContext context;
231 232 233
  Isolate* isolate = GetIsolateFrom(&context);
  Factory* factory = isolate->factory();
  Heap* heap = isolate->heap();
234
  HandleScope scope(isolate);
235 236
  Handle<JSFunction> function =
      factory->NewFunctionForTest(factory->function_string());
237
  Handle<JSObject> key = factory->NewJSObject(function);
238
  Handle<JSWeakMap> weakmap = isolate->factory()->NewJSWeakMap();
239 240

  // Start second old-space page so that values land on evacuation candidate.
241
  Page* first_page = heap->old_space()->first_page();
242
  heap::SimulateFullSpace(heap->old_space());
243 244 245

  // Fill up weak map with values on an evacuation candidate.
  {
246
    HandleScope scope(isolate);
247
    for (int i = 0; i < 32; i++) {
248 249
      Handle<JSObject> object =
          factory->NewJSObject(function, AllocationType::kOld);
250
      CHECK(!Heap::InYoungGeneration(*object));
251
      CHECK(!first_page->Contains(object->address()));
252
      int32_t hash = key->GetOrCreateHash(isolate)->value();
253
      JSWeakCollection::Set(weakmap, key, object, hash);
254 255 256 257 258
    }
  }

  // Force compacting garbage collection.
  CHECK(FLAG_always_compact);
259
  CcTest::CollectAllGarbage();
260
}
261 262 263 264 265


// Test that weak map keys on an evacuation candidate which are reachable by
// other strong paths are correctly recorded in the slots buffer.
TEST(Regress2060b) {
266
  if (i::FLAG_never_compact) return;
267
  FLAG_always_compact = true;
268
#ifdef VERIFY_HEAP
269
  FLAG_verify_heap = true;
270
#endif
271

272
  LocalContext context;
273 274 275
  Isolate* isolate = GetIsolateFrom(&context);
  Factory* factory = isolate->factory();
  Heap* heap = isolate->heap();
276
  HandleScope scope(isolate);
277 278
  Handle<JSFunction> function =
      factory->NewFunctionForTest(factory->function_string());
279 280

  // Start second old-space page so that keys land on evacuation candidate.
281
  Page* first_page = heap->old_space()->first_page();
282
  heap::SimulateFullSpace(heap->old_space());
283 284 285 286

  // Fill up weak map with keys on an evacuation candidate.
  Handle<JSObject> keys[32];
  for (int i = 0; i < 32; i++) {
287
    keys[i] = factory->NewJSObject(function, AllocationType::kOld);
288
    CHECK(!Heap::InYoungGeneration(*keys[i]));
289 290
    CHECK(!first_page->Contains(keys[i]->address()));
  }
291
  Handle<JSWeakMap> weakmap = isolate->factory()->NewJSWeakMap();
292
  for (int i = 0; i < 32; i++) {
yurys's avatar
yurys committed
293
    Handle<Smi> smi(Smi::FromInt(i), isolate);
294
    int32_t hash = keys[i]->GetOrCreateHash(isolate)->value();
295
    JSWeakCollection::Set(weakmap, keys[i], smi, hash);
296 297 298 299 300
  }

  // Force compacting garbage collection. The subsequent collections are used
  // to verify that key references were actually updated.
  CHECK(FLAG_always_compact);
301 302 303
  CcTest::CollectAllGarbage();
  CcTest::CollectAllGarbage();
  CcTest::CollectAllGarbage();
304
}
305 306 307


TEST(Regress399527) {
308
  if (!FLAG_incremental_marking) return;
309 310 311 312 313 314
  CcTest::InitializeVM();
  v8::HandleScope scope(CcTest::isolate());
  Isolate* isolate = CcTest::i_isolate();
  Heap* heap = isolate->heap();
  {
    HandleScope scope(isolate);
315
    isolate->factory()->NewJSWeakMap();
316
    heap::SimulateIncrementalMarking(heap);
317 318 319 320
  }
  // The weak map is marked black here but leaving the handle scope will make
  // the object unreachable. Aborting incremental marking will clear all the
  // marking bits which makes the weak map garbage.
321
  CcTest::CollectAllGarbage();
322
}
323

324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356
TEST(WeakMapsWithChainedEntries) {
  ManualGCScope manual_gc_scope;
  CcTest::InitializeVM();
  v8::Isolate* isolate = CcTest::isolate();
  i::Isolate* i_isolate = CcTest::i_isolate();
  v8::HandleScope scope(isolate);

  const int initial_gc_count = i_isolate->heap()->gc_count();
  Handle<JSWeakMap> weakmap1 = i_isolate->factory()->NewJSWeakMap();
  Handle<JSWeakMap> weakmap2 = i_isolate->factory()->NewJSWeakMap();
  v8::Global<v8::Object> g1;
  v8::Global<v8::Object> g2;
  {
    v8::HandleScope scope(isolate);
    v8::Local<v8::Object> o1 = v8::Object::New(isolate);
    g1.Reset(isolate, o1);
    g1.SetWeak();
    v8::Local<v8::Object> o2 = v8::Object::New(isolate);
    g2.Reset(isolate, o2);
    g2.SetWeak();
    Handle<Object> i_o1 = v8::Utils::OpenHandle(*o1);
    Handle<Object> i_o2 = v8::Utils::OpenHandle(*o2);
    int32_t hash1 = i_o1->GetOrCreateHash(i_isolate)->value();
    int32_t hash2 = i_o2->GetOrCreateHash(i_isolate)->value();
    JSWeakCollection::Set(weakmap1, i_o1, i_o2, hash1);
    JSWeakCollection::Set(weakmap2, i_o2, i_o1, hash2);
  }
  CcTest::CollectGarbage(OLD_SPACE);
  CHECK(g1.IsEmpty());
  CHECK(g2.IsEmpty());
  CHECK_EQ(1, i_isolate->heap()->gc_count() - initial_gc_count);
}

357
}  // namespace test_weakmaps
358 359
}  // namespace internal
}  // namespace v8