marker-unittest.cc 16.7 KB
Newer Older
1 2 3 4 5 6
// Copyright 2020 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "src/heap/cppgc/marker.h"

7 8
#include <memory>

9
#include "include/cppgc/allocation.h"
10
#include "include/cppgc/internal/pointer-policies.h"
11 12
#include "include/cppgc/member.h"
#include "include/cppgc/persistent.h"
13
#include "include/cppgc/trace-trait.h"
14
#include "src/heap/cppgc/heap-object-header.h"
15
#include "src/heap/cppgc/marking-visitor.h"
16
#include "src/heap/cppgc/stats-collector.h"
17 18 19 20 21 22 23 24 25 26 27
#include "test/unittests/heap/cppgc/tests.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace cppgc {
namespace internal {

namespace {
class MarkerTest : public testing::TestWithHeap {
 public:
  using MarkingConfig = Marker::MarkingConfig;

28 29 30
  void DoMarking(MarkingConfig::StackState stack_state) {
    const MarkingConfig config = {MarkingConfig::CollectionType::kMajor,
                                  stack_state};
31
    auto* heap = Heap::From(GetHeap());
32 33
    InitializeMarker(*heap, GetPlatformHandle().get(), config);
    marker_->FinishMarking(stack_state);
34 35 36
    // Pretend do finish sweeping as StatsCollector verifies that Notify*
    // methods are called in the right order.
    heap->stats_collector()->NotifySweepingCompleted();
37
  }
38 39 40 41 42 43 44 45 46 47 48

  void InitializeMarker(HeapBase& heap, cppgc::Platform* platform,
                        MarkingConfig config) {
    marker_ =
        MarkerFactory::CreateAndStartMarking<Marker>(heap, platform, config);
  }

  Marker* marker() const { return marker_.get(); }

 private:
  std::unique_ptr<Marker> marker_;
49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74
};

class GCed : public GarbageCollected<GCed> {
 public:
  void SetChild(GCed* child) { child_ = child; }
  void SetWeakChild(GCed* child) { weak_child_ = child; }
  GCed* child() const { return child_.Get(); }
  GCed* weak_child() const { return weak_child_.Get(); }
  void Trace(cppgc::Visitor* visitor) const {
    visitor->Trace(child_);
    visitor->Trace(weak_child_);
  }

 private:
  Member<GCed> child_;
  WeakMember<GCed> weak_child_;
};

template <typename T>
V8_NOINLINE T access(volatile const T& t) {
  return t;
}

}  // namespace

TEST_F(MarkerTest, PersistentIsMarked) {
75
  Persistent<GCed> object = MakeGarbageCollected<GCed>(GetAllocationHandle());
76 77
  HeapObjectHeader& header = HeapObjectHeader::FromPayload(object);
  EXPECT_FALSE(header.IsMarked());
78
  DoMarking(MarkingConfig::StackState::kNoHeapPointers);
79 80 81 82
  EXPECT_TRUE(header.IsMarked());
}

TEST_F(MarkerTest, ReachableMemberIsMarked) {
83 84
  Persistent<GCed> parent = MakeGarbageCollected<GCed>(GetAllocationHandle());
  parent->SetChild(MakeGarbageCollected<GCed>(GetAllocationHandle()));
85 86
  HeapObjectHeader& header = HeapObjectHeader::FromPayload(parent->child());
  EXPECT_FALSE(header.IsMarked());
87
  DoMarking(MarkingConfig::StackState::kNoHeapPointers);
88 89 90 91
  EXPECT_TRUE(header.IsMarked());
}

TEST_F(MarkerTest, UnreachableMemberIsNotMarked) {
92
  Member<GCed> object = MakeGarbageCollected<GCed>(GetAllocationHandle());
93 94
  HeapObjectHeader& header = HeapObjectHeader::FromPayload(object);
  EXPECT_FALSE(header.IsMarked());
95
  DoMarking(MarkingConfig::StackState::kNoHeapPointers);
96 97 98 99
  EXPECT_FALSE(header.IsMarked());
}

TEST_F(MarkerTest, ObjectReachableFromStackIsMarked) {
100
  GCed* object = MakeGarbageCollected<GCed>(GetAllocationHandle());
101
  EXPECT_FALSE(HeapObjectHeader::FromPayload(object).IsMarked());
102
  DoMarking(MarkingConfig::StackState::kMayContainHeapPointers);
103 104 105 106 107
  EXPECT_TRUE(HeapObjectHeader::FromPayload(object).IsMarked());
  access(object);
}

TEST_F(MarkerTest, ObjectReachableOnlyFromStackIsNotMarkedIfStackIsEmpty) {
108
  GCed* object = MakeGarbageCollected<GCed>(GetAllocationHandle());
109 110
  HeapObjectHeader& header = HeapObjectHeader::FromPayload(object);
  EXPECT_FALSE(header.IsMarked());
111
  DoMarking(MarkingConfig::StackState::kNoHeapPointers);
112 113 114 115 116 117
  EXPECT_FALSE(header.IsMarked());
  access(object);
}

TEST_F(MarkerTest, WeakReferenceToUnreachableObjectIsCleared) {
  {
118 119
    WeakPersistent<GCed> weak_object =
        MakeGarbageCollected<GCed>(GetAllocationHandle());
120
    EXPECT_TRUE(weak_object);
121
    DoMarking(MarkingConfig::StackState::kNoHeapPointers);
122 123 124
    EXPECT_FALSE(weak_object);
  }
  {
125 126
    Persistent<GCed> parent = MakeGarbageCollected<GCed>(GetAllocationHandle());
    parent->SetWeakChild(MakeGarbageCollected<GCed>(GetAllocationHandle()));
127
    EXPECT_TRUE(parent->weak_child());
128
    DoMarking(MarkingConfig::StackState::kNoHeapPointers);
129 130 131 132 133 134 135
    EXPECT_FALSE(parent->weak_child());
  }
}

TEST_F(MarkerTest, WeakReferenceToReachableObjectIsNotCleared) {
  // Reachable from Persistent
  {
136
    Persistent<GCed> object = MakeGarbageCollected<GCed>(GetAllocationHandle());
137 138
    WeakPersistent<GCed> weak_object(object);
    EXPECT_TRUE(weak_object);
139
    DoMarking(MarkingConfig::StackState::kNoHeapPointers);
140 141 142
    EXPECT_TRUE(weak_object);
  }
  {
143 144
    Persistent<GCed> object = MakeGarbageCollected<GCed>(GetAllocationHandle());
    Persistent<GCed> parent = MakeGarbageCollected<GCed>(GetAllocationHandle());
145 146
    parent->SetWeakChild(object);
    EXPECT_TRUE(parent->weak_child());
147
    DoMarking(MarkingConfig::StackState::kNoHeapPointers);
148 149 150 151
    EXPECT_TRUE(parent->weak_child());
  }
  // Reachable from Member
  {
152 153 154
    Persistent<GCed> parent = MakeGarbageCollected<GCed>(GetAllocationHandle());
    WeakPersistent<GCed> weak_object(
        MakeGarbageCollected<GCed>(GetAllocationHandle()));
155 156
    parent->SetChild(weak_object);
    EXPECT_TRUE(weak_object);
157
    DoMarking(MarkingConfig::StackState::kNoHeapPointers);
158 159 160
    EXPECT_TRUE(weak_object);
  }
  {
161 162
    Persistent<GCed> parent = MakeGarbageCollected<GCed>(GetAllocationHandle());
    parent->SetChild(MakeGarbageCollected<GCed>(GetAllocationHandle()));
163 164
    parent->SetWeakChild(parent->child());
    EXPECT_TRUE(parent->weak_child());
165
    DoMarking(MarkingConfig::StackState::kNoHeapPointers);
166 167 168 169
    EXPECT_TRUE(parent->weak_child());
  }
  // Reachable from stack
  {
170
    GCed* object = MakeGarbageCollected<GCed>(GetAllocationHandle());
171 172
    WeakPersistent<GCed> weak_object(object);
    EXPECT_TRUE(weak_object);
173
    DoMarking(MarkingConfig::StackState::kMayContainHeapPointers);
174 175 176 177
    EXPECT_TRUE(weak_object);
    access(object);
  }
  {
178 179
    GCed* object = MakeGarbageCollected<GCed>(GetAllocationHandle());
    Persistent<GCed> parent = MakeGarbageCollected<GCed>(GetAllocationHandle());
180 181
    parent->SetWeakChild(object);
    EXPECT_TRUE(parent->weak_child());
182
    DoMarking(MarkingConfig::StackState::kMayContainHeapPointers);
183 184 185 186 187 188 189
    EXPECT_TRUE(parent->weak_child());
    access(object);
  }
}

TEST_F(MarkerTest, DeepHierarchyIsMarked) {
  static constexpr int kHierarchyDepth = 10;
190
  Persistent<GCed> root = MakeGarbageCollected<GCed>(GetAllocationHandle());
191 192
  GCed* parent = root;
  for (int i = 0; i < kHierarchyDepth; ++i) {
193
    parent->SetChild(MakeGarbageCollected<GCed>(GetAllocationHandle()));
194 195 196
    parent->SetWeakChild(parent->child());
    parent = parent->child();
  }
197
  DoMarking(MarkingConfig::StackState::kNoHeapPointers);
198 199 200 201 202 203 204 205 206
  EXPECT_TRUE(HeapObjectHeader::FromPayload(root).IsMarked());
  parent = root;
  for (int i = 0; i < kHierarchyDepth; ++i) {
    EXPECT_TRUE(HeapObjectHeader::FromPayload(parent->child()).IsMarked());
    EXPECT_TRUE(parent->weak_child());
    parent = parent->child();
  }
}

207
TEST_F(MarkerTest, NestedObjectsOnStackAreMarked) {
208 209 210
  GCed* root = MakeGarbageCollected<GCed>(GetAllocationHandle());
  root->SetChild(MakeGarbageCollected<GCed>(GetAllocationHandle()));
  root->child()->SetChild(MakeGarbageCollected<GCed>(GetAllocationHandle()));
211
  DoMarking(MarkingConfig::StackState::kMayContainHeapPointers);
212 213 214 215 216
  EXPECT_TRUE(HeapObjectHeader::FromPayload(root).IsMarked());
  EXPECT_TRUE(HeapObjectHeader::FromPayload(root->child()).IsMarked());
  EXPECT_TRUE(HeapObjectHeader::FromPayload(root->child()->child()).IsMarked());
}

217
namespace {
218

219 220 221 222 223 224 225
class GCedWithCallback : public GarbageCollected<GCedWithCallback> {
 public:
  template <typename Callback>
  explicit GCedWithCallback(Callback callback) {
    callback(this);
  }

226 227 228 229 230 231 232 233 234 235 236
  template <typename Callback>
  GCedWithCallback(Callback callback, GCed* gced) : gced_(gced) {
    callback(this);
  }

  void Trace(Visitor* visitor) const { visitor->Trace(gced_); }

  GCed* gced() const { return gced_; }

 private:
  Member<GCed> gced_;
237
};
238

239 240 241
}  // namespace

TEST_F(MarkerTest, InConstructionObjectIsEventuallyMarkedEmptyStack) {
242 243 244
  static const Marker::MarkingConfig config = {
      MarkingConfig::CollectionType::kMajor,
      MarkingConfig::StackState::kMayContainHeapPointers};
245
  InitializeMarker(*Heap::From(GetHeap()), GetPlatformHandle().get(), config);
246
  GCedWithCallback* object = MakeGarbageCollected<GCedWithCallback>(
247
      GetAllocationHandle(), [marker = marker()](GCedWithCallback* obj) {
248
        Member<GCedWithCallback> member(obj);
249
        marker->Visitor().Trace(member);
250
      });
251
  EXPECT_FALSE(HeapObjectHeader::FromPayload(object).IsMarked());
252
  marker()->FinishMarking(MarkingConfig::StackState::kMayContainHeapPointers);
253 254 255 256
  EXPECT_TRUE(HeapObjectHeader::FromPayload(object).IsMarked());
}

TEST_F(MarkerTest, InConstructionObjectIsEventuallyMarkedNonEmptyStack) {
257 258 259
  static const Marker::MarkingConfig config = {
      MarkingConfig::CollectionType::kMajor,
      MarkingConfig::StackState::kMayContainHeapPointers};
260
  InitializeMarker(*Heap::From(GetHeap()), GetPlatformHandle().get(), config);
261
  MakeGarbageCollected<GCedWithCallback>(
262
      GetAllocationHandle(), [marker = marker()](GCedWithCallback* obj) {
263
        Member<GCedWithCallback> member(obj);
264
        marker->Visitor().Trace(member);
265
        EXPECT_FALSE(HeapObjectHeader::FromPayload(obj).IsMarked());
266
        marker->FinishMarking(
267
            MarkingConfig::StackState::kMayContainHeapPointers);
268 269
        EXPECT_TRUE(HeapObjectHeader::FromPayload(obj).IsMarked());
      });
270 271
}

272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328
namespace {

// Storage that can be used to hide a pointer from the GC. Only useful when
// dealing with the stack separately.
class GCObliviousObjectStorage final {
 public:
  GCObliviousObjectStorage()
      : storage_(std::make_unique<const void*>(nullptr)) {}

  template <typename T>
  void set_object(T* t) {
    *storage_.get() = TraceTrait<T>::GetTraceDescriptor(t).base_object_payload;
  }

  const void* object() const { return *storage_; }

 private:
  std::unique_ptr<const void*> storage_;
};

V8_NOINLINE void RegisterInConstructionObject(
    AllocationHandle& allocation_handle, Visitor& v,
    GCObliviousObjectStorage& storage) {
  // Create deeper stack to avoid finding any temporary reference in the caller.
  char space[500];
  USE(space);
  MakeGarbageCollected<GCedWithCallback>(
      allocation_handle,
      [&visitor = v, &storage](GCedWithCallback* obj) {
        Member<GCedWithCallback> member(obj);
        // Adds GCedWithCallback to in-construction objects.
        visitor.Trace(member);
        EXPECT_FALSE(HeapObjectHeader::FromPayload(obj).IsMarked());
        // The inner object GCed is only found if GCedWithCallback is processed.
        storage.set_object(obj->gced());
      },
      // Initializing store does not trigger a write barrier.
      MakeGarbageCollected<GCed>(allocation_handle));
}

}  // namespace

TEST_F(MarkerTest,
       InConstructionObjectIsEventuallyMarkedDifferentNonEmptyStack) {
  static const Marker::MarkingConfig config = {
      MarkingConfig::CollectionType::kMajor,
      MarkingConfig::StackState::kMayContainHeapPointers};
  InitializeMarker(*Heap::From(GetHeap()), GetPlatformHandle().get(), config);

  GCObliviousObjectStorage storage;
  RegisterInConstructionObject(GetAllocationHandle(), marker()->Visitor(),
                               storage);
  EXPECT_FALSE(HeapObjectHeader::FromPayload(storage.object()).IsMarked());
  marker()->FinishMarking(MarkingConfig::StackState::kMayContainHeapPointers);
  EXPECT_TRUE(HeapObjectHeader::FromPayload(storage.object()).IsMarked());
}

329 330 331
TEST_F(MarkerTest, SentinelNotClearedOnWeakPersistentHandling) {
  static const Marker::MarkingConfig config = {
      MarkingConfig::CollectionType::kMajor,
332 333
      MarkingConfig::StackState::kNoHeapPointers,
      MarkingConfig::MarkingType::kIncremental};
334 335 336
  Persistent<GCed> root = MakeGarbageCollected<GCed>(GetAllocationHandle());
  auto* tmp = MakeGarbageCollected<GCed>(GetAllocationHandle());
  root->SetWeakChild(tmp);
337 338 339 340 341 342 343
  InitializeMarker(*Heap::From(GetHeap()), GetPlatformHandle().get(), config);
  while (!marker()->IncrementalMarkingStepForTesting(
      MarkingConfig::StackState::kNoHeapPointers)) {
  }
  // {root} object must be marked at this point because we do not allow
  // encountering kSentinelPointer in WeakMember on regular Trace() calls.
  ASSERT_TRUE(HeapObjectHeader::FromPayload(root.Get()).IsMarked());
344
  root->SetWeakChild(kSentinelPointer);
345
  marker()->FinishMarking(MarkingConfig::StackState::kNoHeapPointers);
346 347 348
  EXPECT_EQ(kSentinelPointer, root->weak_child());
}

349 350 351 352 353 354 355 356 357 358 359
// Incremental Marking

class IncrementalMarkingTest : public testing::TestWithHeap {
 public:
  using MarkingConfig = Marker::MarkingConfig;

  static constexpr MarkingConfig IncrementalPreciseMarkingConfig = {
      MarkingConfig::CollectionType::kMajor,
      MarkingConfig::StackState::kNoHeapPointers,
      MarkingConfig::MarkingType::kIncremental};

360
  void FinishSteps(MarkingConfig::StackState stack_state) {
361 362
    while (!SingleStep(stack_state)) {
    }
363 364
  }

365
  void FinishMarking() {
366 367
    GetMarkerRef()->FinishMarking(
        MarkingConfig::StackState::kMayContainHeapPointers);
368 369
    // Pretend do finish sweeping as StatsCollector verifies that Notify*
    // methods are called in the right order.
370
    GetMarkerRef().reset();
371 372 373
    Heap::From(GetHeap())->stats_collector()->NotifySweepingCompleted();
  }

374 375
  void InitializeMarker(HeapBase& heap, cppgc::Platform* platform,
                        MarkingConfig config) {
376
    GetMarkerRef() =
377 378 379
        MarkerFactory::CreateAndStartMarking<Marker>(heap, platform, config);
  }

380
  MarkerBase* marker() const { return GetMarkerRef().get(); }
381

382
 private:
383
  bool SingleStep(MarkingConfig::StackState stack_state) {
384
    return GetMarkerRef()->IncrementalMarkingStepForTesting(stack_state);
385 386
  }
};
387

388 389 390
constexpr IncrementalMarkingTest::MarkingConfig
    IncrementalMarkingTest::IncrementalPreciseMarkingConfig;

391
TEST_F(IncrementalMarkingTest, RootIsMarkedAfterMarkingStarted) {
392 393
  Persistent<GCed> root = MakeGarbageCollected<GCed>(GetAllocationHandle());
  EXPECT_FALSE(HeapObjectHeader::FromPayload(root).IsMarked());
394 395
  InitializeMarker(*Heap::From(GetHeap()), GetPlatformHandle().get(),
                   IncrementalPreciseMarkingConfig);
396
  EXPECT_TRUE(HeapObjectHeader::FromPayload(root).IsMarked());
397
  FinishMarking();
398 399 400 401 402 403 404
}

TEST_F(IncrementalMarkingTest, MemberIsMarkedAfterMarkingSteps) {
  Persistent<GCed> root = MakeGarbageCollected<GCed>(GetAllocationHandle());
  root->SetChild(MakeGarbageCollected<GCed>(GetAllocationHandle()));
  HeapObjectHeader& header = HeapObjectHeader::FromPayload(root->child());
  EXPECT_FALSE(header.IsMarked());
405 406 407
  InitializeMarker(*Heap::From(GetHeap()), GetPlatformHandle().get(),
                   IncrementalPreciseMarkingConfig);
  FinishSteps(MarkingConfig::StackState::kNoHeapPointers);
408
  EXPECT_TRUE(header.IsMarked());
409
  FinishMarking();
410 411 412 413 414
}

TEST_F(IncrementalMarkingTest,
       MemberWithWriteBarrierIsMarkedAfterMarkingSteps) {
  Persistent<GCed> root = MakeGarbageCollected<GCed>(GetAllocationHandle());
415 416
  InitializeMarker(*Heap::From(GetHeap()), GetPlatformHandle().get(),
                   IncrementalPreciseMarkingConfig);
417
  root->SetChild(MakeGarbageCollected<GCed>(GetAllocationHandle()));
418
  FinishSteps(MarkingConfig::StackState::kNoHeapPointers);
419
  HeapObjectHeader& header = HeapObjectHeader::FromPayload(root->child());
420
  EXPECT_TRUE(header.IsMarked());
421
  FinishMarking();
422 423 424 425 426 427 428 429 430 431 432 433 434 435
}

namespace {
class Holder : public GarbageCollected<Holder> {
 public:
  void Trace(Visitor* visitor) const { visitor->Trace(member_); }

  Member<GCedWithCallback> member_;
};
}  // namespace

TEST_F(IncrementalMarkingTest, IncrementalStepDuringAllocation) {
  Persistent<Holder> holder =
      MakeGarbageCollected<Holder>(GetAllocationHandle());
436 437
  InitializeMarker(*Heap::From(GetHeap()), GetPlatformHandle().get(),
                   IncrementalPreciseMarkingConfig);
438 439
  const HeapObjectHeader* header;
  MakeGarbageCollected<GCedWithCallback>(
440
      GetAllocationHandle(), [this, &holder, &header](GCedWithCallback* obj) {
441 442 443
        header = &HeapObjectHeader::FromPayload(obj);
        holder->member_ = obj;
        EXPECT_FALSE(header->IsMarked());
444
        FinishSteps(MarkingConfig::StackState::kMayContainHeapPointers);
445
        EXPECT_FALSE(header->IsMarked());
446
      });
447
  FinishSteps(MarkingConfig::StackState::kNoHeapPointers);
448
  EXPECT_TRUE(header->IsMarked());
449
  FinishMarking();
450 451
}

452 453
TEST_F(IncrementalMarkingTest, MarkingRunsOutOfWorkEventually) {
  InitializeMarker(*Heap::From(GetHeap()), GetPlatformHandle().get(),
454
                   IncrementalPreciseMarkingConfig);
455 456 457 458
  FinishSteps(MarkingConfig::StackState::kNoHeapPointers);
  FinishMarking();
}

459 460
}  // namespace internal
}  // namespace cppgc