#include "src/heap/cppgc/sweeper.h"
#include <algorithm>
#include <atomic>
#include <cstdint>
#include <memory>
#include <optional>
#include <vector>
#include "include/cppgc/platform.h"
#include "src/base/platform/mutex.h"
#include "src/base/platform/time.h"
#include "src/heap/cppgc/free-list.h"
#include "src/heap/cppgc/globals.h"
#include "src/heap/cppgc/heap-base.h"
#include "src/heap/cppgc/heap-config.h"
#include "src/heap/cppgc/heap-object-header.h"
#include "src/heap/cppgc/heap-page.h"
#include "src/heap/cppgc/heap-space.h"
#include "src/heap/cppgc/heap-visitor.h"
#include "src/heap/cppgc/memory.h"
#include "src/heap/cppgc/object-poisoner.h"
#include "src/heap/cppgc/object-start-bitmap.h"
#include "src/heap/cppgc/page-memory.h"
#include "src/heap/cppgc/raw-heap.h"
#include "src/heap/cppgc/stats-collector.h"
#include "src/heap/cppgc/task-handle.h"
namespace cppgc::internal {
namespace {
constexpr TaskPriority kBackgroundBoostedPriority = TaskPriority::kUserBlocking;
constexpr TaskPriority kBackgroundRegularPriority = TaskPriority::kUserVisible;
constexpr TaskPriority kForegroundRegularPriority = TaskPriority::kUserBlocking;
constexpr TaskPriority kForegroundLowPriority = TaskPriority::kUserVisible;
class DeadlineChecker final {
public:
explicit DeadlineChecker(v8::base::TimeTicks end) : end_(end) {}
bool Check() {
return V8_UNLIKELY(++count_ % kInterval == 0) &&
(end_ < v8::base::TimeTicks::Now());
}
private:
static constexpr size_t kInterval = 4;
const v8::base::TimeTicks end_;
size_t count_ = 0;
};
enum class MutatorThreadSweepingMode {
kOnlyFinalizers,
kAll,
};
constexpr const char* ToString(MutatorThreadSweepingMode sweeping_mode) {
switch (sweeping_mode) {
case MutatorThreadSweepingMode::kAll:
return "all";
case MutatorThreadSweepingMode::kOnlyFinalizers:
return "only-finalizers";
}
}
class ObjectStartBitmapVerifier final
: private HeapVisitor<ObjectStartBitmapVerifier> {
friend class HeapVisitor<ObjectStartBitmapVerifier>;
public:
void Verify(RawHeap& heap) {
#if DEBUG
Traverse(heap);
#endif
}
void Verify(NormalPage& page) {
#if DEBUG
Traverse(page);
#endif
}
private:
bool VisitNormalPage(NormalPage& page) {
bitmap_ = &page.object_start_bitmap();
prev_ = nullptr;
return false;
}
bool VisitHeapObjectHeader(HeapObjectHeader& header) {
if (header.IsLargeObject()) return true;
auto* raw_header = reinterpret_cast<ConstAddress>(&header);
CHECK(bitmap_->CheckBit<AccessMode::kAtomic>(raw_header));
if (prev_) {
CHECK_EQ(prev_, bitmap_->FindHeader<AccessMode::kAtomic>(raw_header - 1));
}
prev_ = &header;
return true;
}
PlatformAwareObjectStartBitmap* bitmap_ = nullptr;
HeapObjectHeader* prev_ = nullptr;
};
class FreeHandlerBase {
public:
virtual ~FreeHandlerBase() = default;
virtual void FreeFreeList(
std::vector<FreeList::Block>& unfinalized_free_list) = 0;
};
class DiscardingFreeHandler : public FreeHandlerBase {
public:
DiscardingFreeHandler(PageAllocator& page_allocator, FreeList& free_list,
BasePage& page)
: page_allocator_(page_allocator), free_list_(free_list), page_(page) {}
void Free(FreeList::Block block) {
const auto unused_range = free_list_.AddReturningUnusedBounds(block);
const uintptr_t aligned_begin_unused =
RoundUp(reinterpret_cast<uintptr_t>(unused_range.first),
page_allocator_.CommitPageSize());
const uintptr_t aligned_end_unused =
RoundDown(reinterpret_cast<uintptr_t>(unused_range.second),
page_allocator_.CommitPageSize());
if (aligned_begin_unused < aligned_end_unused) {
const size_t discarded_size = aligned_end_unused - aligned_begin_unused;
page_allocator_.DiscardSystemPages(
reinterpret_cast<void*>(aligned_begin_unused),
aligned_end_unused - aligned_begin_unused);
page_.IncrementDiscardedMemory(discarded_size);
page_.space()
.raw_heap()
->heap()
->stats_collector()
->IncrementDiscardedMemory(discarded_size);
}
}
void FreeFreeList(std::vector<FreeList::Block>& unfinalized_free_list) final {
for (auto entry : unfinalized_free_list) {
Free(std::move(entry));
}
}
private:
PageAllocator& page_allocator_;
FreeList& free_list_;
BasePage& page_;
};
class RegularFreeHandler : public FreeHandlerBase {
public:
RegularFreeHandler(PageAllocator& page_allocator, FreeList& free_list,
BasePage& page)
: free_list_(free_list) {}
void Free(FreeList::Block block) { free_list_.Add(std::move(block)); }
void FreeFreeList(std::vector<FreeList::Block>& unfinalized_free_list) final {
for (auto entry : unfinalized_free_list) {
Free(std::move(entry));
}
}
private:
FreeList& free_list_;
};
template <typename T>
class ThreadSafeStack {
public:
ThreadSafeStack() = default;
void Push(T t) {
v8::base::MutexGuard lock(&mutex_);
vector_.push_back(std::move(t));
is_empty_.store(false, std::memory_order_relaxed);
}
std::optional<T> Pop() {
v8::base::MutexGuard lock(&mutex_);
if (vector_.empty()) {
is_empty_.store(true, std::memory_order_relaxed);
return std::nullopt;
}
T top = std::move(vector_.back());
vector_.pop_back();
return std::move(top);
}
template <typename It>
void Insert(It begin, It end) {
v8::base::MutexGuard lock(&mutex_);
vector_.insert(vector_.end(), begin, end);
is_empty_.store(false, std::memory_order_relaxed);
}
bool IsEmpty() const { return is_empty_.load(std::memory_order_relaxed); }
private:
mutable v8::base::Mutex mutex_;
std::vector<T> vector_;
std::atomic<bool> is_empty_{true};
};
struct SweepingState {
struct SweptPageState {
BasePage* page = nullptr;
#if defined(CPPGC_CAGED_HEAP)
HeapObjectHeader* unfinalized_objects_head = nullptr;
#else
std::vector<HeapObjectHeader*> unfinalized_objects;
#endif
FreeList cached_free_list;
std::vector<FreeList::Block> unfinalized_free_list;
bool is_empty = false;
size_t largest_new_free_list_entry = 0;
};
ThreadSafeStack<BasePage*> unswept_pages;
ThreadSafeStack<SweptPageState> swept_unfinalized_pages;
};
using SpaceStates = std::vector<SweepingState>;
void StickyUnmark(HeapObjectHeader* header, StickyBits sticky_bits) {
#if defined(CPPGC_YOUNG_GENERATION)
if (sticky_bits == StickyBits::kDisabled)
header->Unmark<AccessMode::kAtomic>();
#else
header->Unmark<AccessMode::kAtomic>();
#endif
}
class InlinedFinalizationBuilderBase {
public:
struct ResultType {
bool is_empty = false;
size_t largest_new_free_list_entry = 0;
};
protected:
ResultType result_;
};
template <typename FreeHandler>
class InlinedFinalizationBuilder final : public InlinedFinalizationBuilderBase,
public FreeHandler {
public:
InlinedFinalizationBuilder(BasePage& page, PageAllocator& page_allocator)
: FreeHandler(page_allocator,
NormalPageSpace::From(page.space()).free_list(), page) {}
void AddFinalizer(HeapObjectHeader* header, size_t size) {
header->Finalize();
SetMemoryInaccessible(header, size);
}
void AddFreeListEntry(Address start, size_t size) {
FreeHandler::Free({start, size});
result_.largest_new_free_list_entry =
std::max(result_.largest_new_free_list_entry, size);
}
ResultType&& GetResult(bool is_empty) {
result_.is_empty = is_empty;
return std::move(result_);
}
};
template <typename FreeHandler>
class DeferredFinalizationBuilder final : public FreeHandler {
public:
using ResultType = SweepingState::SweptPageState;
DeferredFinalizationBuilder(BasePage& page, PageAllocator& page_allocator)
: FreeHandler(page_allocator, result_.cached_free_list, page) {
result_.page = &page;
}
void AddFinalizer(HeapObjectHeader* header, size_t size) {
if (header->IsFinalizable()) {
#if defined(CPPGC_CAGED_HEAP)
if (!current_unfinalized_) {
DCHECK_NULL(result_.unfinalized_objects_head);
current_unfinalized_ = header;
result_.unfinalized_objects_head = header;
} else {
current_unfinalized_->SetNextUnfinalized(header);
current_unfinalized_ = header;
}
#else
result_.unfinalized_objects.push_back({header});
#endif
found_finalizer_ = true;
} else {
SetMemoryInaccessible(header, size);
}
}
void AddFreeListEntry(Address start, size_t size) {
if (found_finalizer_) {
result_.unfinalized_free_list.push_back({start, size});
} else {
FreeHandler::Free({start, size});
}
result_.largest_new_free_list_entry =
std::max(result_.largest_new_free_list_entry, size);
found_finalizer_ = false;
}
ResultType&& GetResult(bool is_empty) {
result_.is_empty = is_empty;
return std::move(result_);
}
private:
ResultType result_;
HeapObjectHeader* current_unfinalized_ = nullptr;
bool found_finalizer_ = false;
};
template <typename FinalizationBuilder>
typename FinalizationBuilder::ResultType SweepNormalPage(
NormalPage* page, PageAllocator& page_allocator, StickyBits sticky_bits) {
constexpr auto kAtomicAccess = AccessMode::kAtomic;
FinalizationBuilder builder(*page, page_allocator);
PlatformAwareObjectStartBitmap& bitmap = page->object_start_bitmap();
size_t live_bytes = 0;
Address start_of_gap = page->PayloadStart();
const auto clear_bit_if_coalesced_entry = [&bitmap,
&start_of_gap](Address address) {
if (address != start_of_gap) {
bitmap.ClearBit<AccessMode::kAtomic>(address);
} else {
DCHECK(bitmap.CheckBit<AccessMode::kAtomic>(address));
}
};
for (Address begin = page->PayloadStart(), end = page->PayloadEnd();
begin != end;) {
DCHECK(bitmap.CheckBit<AccessMode::kAtomic>(begin));
HeapObjectHeader* header = reinterpret_cast<HeapObjectHeader*>(begin);
const size_t size = header->AllocatedSize();
if (header->IsFree<kAtomicAccess>()) {
SetMemoryInaccessible(header, std::min(kFreeListEntrySize, size));
CheckMemoryIsInaccessible(header, size);
clear_bit_if_coalesced_entry(begin);
begin += size;
continue;
}
if (!header->IsMarked<kAtomicAccess>()) {
builder.AddFinalizer(header, size);
clear_bit_if_coalesced_entry(begin);
begin += size;
continue;
}
const Address header_address = reinterpret_cast<Address>(header);
if (start_of_gap != header_address) {
const size_t new_free_list_entry_size =
static_cast<size_t>(header_address - start_of_gap);
builder.AddFreeListEntry(start_of_gap, new_free_list_entry_size);
DCHECK(bitmap.CheckBit<AccessMode::kAtomic>(start_of_gap));
}
StickyUnmark(header, sticky_bits);
begin += size;
start_of_gap = begin;
live_bytes += size;
}
const bool is_empty = live_bytes == 0;
CHECK_EQ(is_empty, page->marked_bytes() == 0);
CHECK_IMPLIES(is_empty, start_of_gap == page->PayloadStart());
if (!is_empty && start_of_gap != page->PayloadEnd()) {
builder.AddFreeListEntry(
start_of_gap, static_cast<size_t>(page->PayloadEnd() - start_of_gap));
DCHECK(bitmap.CheckBit<AccessMode::kAtomic>(start_of_gap));
}
page->SetAllocatedBytesAtLastGC(live_bytes);
page->ResetMarkedBytes(sticky_bits == StickyBits::kDisabled ? 0 : live_bytes);
return builder.GetResult(is_empty);
}
constexpr BaseSpace* kSweepWithoutSpaceAssignment = nullptr;
enum class EmptyPageHandling {
kDestroy,
kReturn,
};
class SweepFinalizer final {
using FreeMemoryHandling = SweepingConfig::FreeMemoryHandling;
public:
SweepFinalizer(cppgc::Platform* platform, StatsCollector* stats_collector,
BaseSpace* space, size_t* unused_destroyed_normal_pages,
FreeMemoryHandling free_memory_handling,
EmptyPageHandling empty_page_handling_type)
: platform_(platform),
stats_collector_(stats_collector),
space_(space),
unused_destroyed_normal_pages_(unused_destroyed_normal_pages),
free_memory_handling_(free_memory_handling),
empty_page_handling_(empty_page_handling_type) {}
void Finalize(SpaceStates& states) {
for (SweepingState& state : states) {
Finalize(state);
}
}
void Finalize(SweepingState& state) {
while (auto page_state = state.swept_unfinalized_pages.Pop()) {
FinalizePage(&*page_state);
}
}
bool FinalizeWithDeadlineAndSize(StatsCollector::ScopeId scope_id,
SweepingState& state,
v8::base::TimeTicks deadline, size_t size) {
if (state.swept_unfinalized_pages.IsEmpty()) {
return false;
}
StatsCollector::DisabledScope finalize_scope(stats_collector_, scope_id);
DeadlineChecker deadline_check(deadline);
while (auto page_state = state.swept_unfinalized_pages.Pop()) {
FinalizePage(&*page_state);
if (size <= largest_consecutive_block_) {
return true;
}
if (deadline_check.Check()) {
break;
}
}
return false;
}
bool FinalizeWithDeadline(StatsCollector::ScopeId scope_id,
SweepingState& state,
v8::base::TimeTicks deadline) {
if (state.swept_unfinalized_pages.IsEmpty()) {
return true;
}
StatsCollector::DisabledScope finalize_scope(stats_collector_, scope_id);
DeadlineChecker deadline_check(deadline);
while (auto page_state = state.swept_unfinalized_pages.Pop()) {
FinalizePage(&*page_state);
if (deadline_check.Check()) {
return false;
}
}
return true;
}
private:
void FinalizePage(SweepingState::SweptPageState* page_state) {
DCHECK(page_state);
DCHECK(page_state->page);
BasePage* page = page_state->page;
const auto finalize_header = [](HeapObjectHeader* header) {
const size_t size = header->AllocatedSize();
header->Finalize();
SetMemoryInaccessible(header, size);
};
#if defined(CPPGC_CAGED_HEAP)
#if defined(CPPGC_POINTER_COMPRESSION)
const uint64_t cage_base = CageBaseGlobal::Get();
#else
const uint64_t cage_base = CagedHeapBase::GetBase();
#endif
HeapObjectHeader* next_unfinalized = nullptr;
for (auto* unfinalized_header = page_state->unfinalized_objects_head;
unfinalized_header; unfinalized_header = next_unfinalized) {
next_unfinalized = unfinalized_header->GetNextUnfinalized(cage_base);
finalize_header(unfinalized_header);
}
#else
for (HeapObjectHeader* unfinalized_header :
page_state->unfinalized_objects) {
finalize_header(unfinalized_header);
}
#endif
if (page_state->is_empty) {
DCHECK_IMPLIES(page->is_large(),
empty_page_handling_ == EmptyPageHandling::kDestroy);
if (empty_page_handling_ == EmptyPageHandling::kDestroy) {
if (!page->is_large()) {
(*unused_destroyed_normal_pages_)++;
} else {
largest_consecutive_block_ = std::max(
LargePage::From(page)->PayloadSize(), largest_consecutive_block_);
}
BasePage::Destroy(page);
return;
}
auto* normal_page = NormalPage::From(page);
if (space_) {
normal_page->ChangeOwner(*space_);
}
page_state->cached_free_list.Clear();
page_state->cached_free_list.Add(
{normal_page->PayloadStart(), normal_page->PayloadSize()});
page_state->unfinalized_free_list.clear();
page_state->largest_new_free_list_entry = normal_page->PayloadSize();
}
DCHECK_IMPLIES(space_, space_ == &page->space());
DCHECK(!page->is_large());
FreeList& space_freelist = NormalPageSpace::From(page->space()).free_list();
space_freelist.Append(std::move(page_state->cached_free_list));
if (!page_state->unfinalized_free_list.empty()) {
std::unique_ptr<FreeHandlerBase> handler =
free_memory_handling_ == FreeMemoryHandling::kDiscardWherePossible
? std::unique_ptr<FreeHandlerBase>(new DiscardingFreeHandler(
*platform_->GetPageAllocator(), space_freelist, *page))
: std::unique_ptr<FreeHandlerBase>(new RegularFreeHandler(
*platform_->GetPageAllocator(), space_freelist, *page));
handler->FreeFreeList(page_state->unfinalized_free_list);
}
largest_consecutive_block_ = std::max(
page_state->largest_new_free_list_entry, largest_consecutive_block_);
ObjectStartBitmapVerifier().Verify(static_cast<NormalPage&>(*page));
page->space().AddPage(page);
}
cppgc::Platform* platform_;
StatsCollector* stats_collector_;
BaseSpace* space_;
size_t* unused_destroyed_normal_pages_;
size_t largest_consecutive_block_ = 0;
const FreeMemoryHandling free_memory_handling_;
const EmptyPageHandling empty_page_handling_;
};
class MutatorThreadSweeper final : private HeapVisitor<MutatorThreadSweeper> {
friend class HeapVisitor<MutatorThreadSweeper>;
using FreeMemoryHandling = SweepingConfig::FreeMemoryHandling;
public:
MutatorThreadSweeper(HeapBase* heap, cppgc::Platform* platform,
StatsCollector* stats_collector, BaseSpace* space,
size_t* unused_destroyed_normal_pages,
FreeMemoryHandling free_memory_handling,
EmptyPageHandling empty_page_handling)
: platform_(platform),
stats_collector_(stats_collector),
space_(space),
unused_destroyed_normal_pages_(unused_destroyed_normal_pages),
free_memory_handling_(free_memory_handling),
empty_page_handling_(empty_page_handling),
sticky_bits_(heap->sticky_bits()) {}
static void SweepLiveLargePage(LargePage& page, StickyBits sticky_bits) {
HeapObjectHeader* header = page.ObjectHeader();
CHECK(header->IsMarked());
StickyUnmark(header, sticky_bits);
if (sticky_bits == StickyBits::kDisabled) {
page.ResetMarkedBytes();
}
page.space().AddPage(&page);
}
void Sweep(SpaceStates& states) {
for (SweepingState& state : states) {
Sweep(state);
}
}
void Sweep(SweepingState& state) {
while (auto page = state.unswept_pages.Pop()) {
SweepPage(**page);
}
}
void SweepPage(BasePage& page) { Traverse(page); }
bool FinalizeAndSweepWithDeadline(StatsCollector::ScopeId scope_id,
SweepingState& state,
v8::base::TimeTicks deadline,
MutatorThreadSweepingMode sweeping_mode) {
SweepFinalizer finalizer(
platform_, stats_collector_, space_, unused_destroyed_normal_pages_,
free_memory_handling_, EmptyPageHandling::kDestroy);
if (!finalizer.FinalizeWithDeadline(scope_id, state, deadline)) {
return false;
}
if (sweeping_mode != MutatorThreadSweepingMode::kOnlyFinalizers) {
if (!SweepSpaceWithDeadline(&state, deadline)) {
return false;
}
}
return true;
}
bool SweepWithDeadlineAndSize(StatsCollector::ScopeId scope_id,
SweepingState& state,
v8::base::TimeTicks deadline, size_t size) {
if (state.unswept_pages.IsEmpty()) {
return false;
}
StatsCollector::DisabledScope sweep_scope(stats_collector_, scope_id);
DeadlineChecker deadline_check(deadline);
while (auto page = state.unswept_pages.Pop()) {
SweepPage(**page);
if (size <= largest_consecutive_block_) {
return true;
}
if (deadline_check.Check()) {
break;
}
}
return false;
}
private:
bool SweepSpaceWithDeadline(SweepingState* state,
v8::base::TimeTicks deadline) {
DeadlineChecker deadline_check(deadline);
while (auto page = state->unswept_pages.Pop()) {
Traverse(**page);
if (deadline_check.Check()) {
return false;
}
}
return true;
}
bool VisitNormalPage(NormalPage& page) {
if (free_memory_handling_ == FreeMemoryHandling::kDiscardWherePossible) {
page.ResetDiscardedMemory();
}
const auto result =
free_memory_handling_ == FreeMemoryHandling::kDiscardWherePossible
? SweepNormalPage<
InlinedFinalizationBuilder<DiscardingFreeHandler>>(
&page, *platform_->GetPageAllocator(), sticky_bits_)
: SweepNormalPage<InlinedFinalizationBuilder<RegularFreeHandler>>(
&page, *platform_->GetPageAllocator(), sticky_bits_);
if (result.is_empty &&
empty_page_handling_ == EmptyPageHandling::kDestroy) {
NormalPage::Destroy(&page);
(*unused_destroyed_normal_pages_)++;
} else {
if (space_) {
DCHECK_IMPLIES(!result.is_empty, space_ == &page.space());
page.ChangeOwner(*space_);
}
auto& target_space = NormalPageSpace::From(page.space());
target_space.AddPage(&page);
if (result.is_empty) {
target_space.free_list().Add({page.PayloadStart(), page.PayloadSize()});
}
ObjectStartBitmapVerifier().Verify(page);
largest_consecutive_block_ =
std::max(result.is_empty ? page.PayloadSize()
: result.largest_new_free_list_entry,
largest_consecutive_block_);
}
return true;
}
bool VisitLargePage(LargePage& page) {
HeapObjectHeader* header = page.ObjectHeader();
CHECK(!header->IsMarked());
DCHECK_EQ(page.marked_bytes(), 0u);
header->Finalize();
largest_consecutive_block_ =
std::max(page.PayloadSize(), largest_consecutive_block_);
LargePage::Destroy(&page);
return true;
}
cppgc::Platform* platform_;
StatsCollector* stats_collector_;
size_t largest_consecutive_block_ = 0;
BaseSpace* space_;
size_t* unused_destroyed_normal_pages_;
const FreeMemoryHandling free_memory_handling_;
const EmptyPageHandling empty_page_handling_;
const StickyBits sticky_bits_;
};
class ConcurrentSweepTask final : public cppgc::JobTask,
private HeapVisitor<ConcurrentSweepTask> {
friend class HeapVisitor<ConcurrentSweepTask>;
using FreeMemoryHandling = SweepingConfig::FreeMemoryHandling;
public:
ConcurrentSweepTask(Platform* platform, HeapBase& heap,
SpaceStates* space_states,
SweepingState* empty_normal_pages,
SweepingState* empty_large_pages,
FreeMemoryHandling free_memory_handling)
: heap_(heap),
page_allocator_(*platform->GetPageAllocator()),
space_states_(space_states),
empty_normal_pages_(empty_normal_pages),
empty_large_pages_(empty_large_pages),
free_memory_handling_(free_memory_handling),
sticky_bits_(heap.sticky_bits()) {}
void Run(cppgc::JobDelegate* delegate) final {
StatsCollector::EnabledConcurrentScope stats_scope(
heap_.stats_collector(), StatsCollector::kConcurrentSweep);
if (!SweepStateOrYield(delegate, *empty_normal_pages_)) return;
for (SweepingState& state : *space_states_) {
if (!SweepStateOrYield(delegate, state)) return;
}
if (!SweepStateOrYield(delegate, *empty_large_pages_)) return;
is_completed_.store(true, std::memory_order_relaxed);
}
size_t GetMaxConcurrency(size_t ) const final {
return is_completed_.load(std::memory_order_relaxed) ? 0 : 1;
}
private:
bool SweepStateOrYield(cppgc::JobDelegate* delegate, SweepingState& state) {
current_sweeping_state_ = &state;
while (auto page = state.unswept_pages.Pop()) {
Traverse(**page);
if (delegate->ShouldYield()) {
return false;
}
}
current_sweeping_state_ = nullptr;
return true;
}
bool VisitNormalPage(NormalPage& page) {
if (free_memory_handling_ == FreeMemoryHandling::kDiscardWherePossible) {
page.ResetDiscardedMemory();
}
SweepingState::SweptPageState sweep_result =
(free_memory_handling_ == FreeMemoryHandling::kDiscardWherePossible)
? SweepNormalPage<
DeferredFinalizationBuilder<DiscardingFreeHandler>>(
&page, page_allocator_, sticky_bits_)
: SweepNormalPage<DeferredFinalizationBuilder<RegularFreeHandler>>(
&page, page_allocator_, sticky_bits_);
current_sweeping_state_->swept_unfinalized_pages.Push(
std::move(sweep_result));
return true;
}
bool VisitLargePage(LargePage& page) {
HeapObjectHeader* header = page.ObjectHeader();
CHECK(!header->IsMarked());
DCHECK_EQ(page.marked_bytes(), 0u);
#if defined(CPPGC_CAGED_HEAP)
HeapObjectHeader* const unfinalized_objects =
header->IsFinalizable() ? page.ObjectHeader() : nullptr;
#else
std::vector<HeapObjectHeader*> unfinalized_objects;
if (header->IsFinalizable()) {
unfinalized_objects.push_back(page.ObjectHeader());
}
#endif
current_sweeping_state_->swept_unfinalized_pages.Push(
{&page, std::move(unfinalized_objects), {}, {}, true});
return true;
}
HeapBase& heap_;
PageAllocator& page_allocator_;
SpaceStates* const space_states_;
SweepingState* const empty_normal_pages_;
SweepingState* const empty_large_pages_;
SweepingState* current_sweeping_state_ = nullptr;
std::atomic_bool is_completed_{false};
const FreeMemoryHandling free_memory_handling_;
const StickyBits sticky_bits_;
};
class PrepareForSweepVisitor final
: protected HeapVisitor<PrepareForSweepVisitor> {
friend class HeapVisitor<PrepareForSweepVisitor>;
using CompactableSpaceHandling = SweepingConfig::CompactableSpaceHandling;
public:
PrepareForSweepVisitor(HeapBase* heap, SpaceStates* space_states,
SweepingState* empty_normal_pages,
SweepingState* empty_large_pages,
CompactableSpaceHandling compactable_space_handling)
: heap_(heap),
space_states_(space_states),
empty_normal_pages_(empty_normal_pages),
empty_large_pages_(empty_large_pages),
compactable_space_handling_(compactable_space_handling) {}
void Run(RawHeap& raw_heap) {
*space_states_ = SpaceStates(raw_heap.size());
Traverse(raw_heap);
}
protected:
bool VisitNormalPageSpace(NormalPageSpace& space) {
if ((compactable_space_handling_ == CompactableSpaceHandling::kIgnore) &&
space.is_compactable()) {
return true;
}
CHECK(!space.linear_allocation_buffer().size());
space.free_list().Clear();
#ifdef V8_USE_ADDRESS_SANITIZER
UnmarkedObjectsPoisoner().Traverse(space);
#endif
BaseSpace::Pages space_pages = space.RemoveAllPages();
std::sort(space_pages.begin(), space_pages.end(),
[](const BasePage* a, const BasePage* b) {
return a->marked_bytes() < b->marked_bytes();
});
auto first_non_empty_page = std::find_if(
space_pages.begin(), space_pages.end(),
[](const BasePage* page) { return page->marked_bytes() != 0; });
empty_normal_pages_->unswept_pages.Insert(space_pages.begin(),
first_non_empty_page);
(*space_states_)[space.index()].unswept_pages.Insert(first_non_empty_page,
space_pages.end());
return true;
}
bool VisitLargePageSpace(LargePageSpace& space) {
#ifdef V8_USE_ADDRESS_SANITIZER
UnmarkedObjectsPoisoner().Traverse(space);
#endif
BaseSpace::Pages space_pages = space.RemoveAllPages();
for (BasePage* page : space_pages) {
#ifdef DEBUG
const auto* header = LargePage::From(page)->ObjectHeader();
DCHECK_IMPLIES(page->marked_bytes() == 0, !header->IsMarked());
DCHECK_IMPLIES(page->marked_bytes() != 0, header->IsMarked());
#endif
if (page->marked_bytes() != 0) {
MutatorThreadSweeper::SweepLiveLargePage(*LargePage::From(page),
heap_->sticky_bits());
} else {
empty_large_pages_->unswept_pages.Push(page);
}
}
return true;
}
private:
HeapBase* const heap_;
SpaceStates* const space_states_;
SweepingState* const empty_normal_pages_;
SweepingState* const empty_large_pages_;
CompactableSpaceHandling compactable_space_handling_;
};
}
class Sweeper::SweeperImpl final {
using FreeMemoryHandling = SweepingConfig::FreeMemoryHandling;
public:
explicit SweeperImpl(HeapBase& heap)
: heap_(heap.raw_heap()),
page_pool_(heap.page_backend()->page_pool()),
stats_collector_(heap.stats_collector()),
platform_(heap.platform()) {
CHECK_NOT_NULL(platform_);
}
~SweeperImpl() { CancelAllSweepingTasks(); }
void Start(SweepingConfig config) {
StatsCollector::EnabledScope stats_scope(stats_collector_,
StatsCollector::kAtomicSweep);
is_in_progress_ = true;
config_ = config;
if (!foreground_task_runner_) {
foreground_task_runner_ =
platform_->GetForegroundTaskRunner(kForegroundRegularPriority);
low_priority_foreground_task_runner_ =
platform_->GetForegroundTaskRunner(kForegroundLowPriority);
CHECK_IMPLIES(low_priority_foreground_task_runner_.get(),
foreground_task_runner_.get());
const auto supports_non_nestable_tasks =
[](const std::shared_ptr<TaskRunner>& runner) {
return runner && runner->NonNestableTasksEnabled() &&
runner->NonNestableDelayedTasksEnabled();
};
if (!supports_non_nestable_tasks(foreground_task_runner_) ||
!supports_non_nestable_tasks(low_priority_foreground_task_runner_)) {
foreground_task_runner_.reset();
low_priority_foreground_task_runner_.reset();
}
}
ObjectStartBitmapVerifier().Verify(heap_);
if (!CanDiscardMemory()) {
config_.free_memory_handling = FreeMemoryHandling::kDoNotDiscard;
}
if (config_.free_memory_handling ==
FreeMemoryHandling::kDiscardWherePossible) {
heap_.heap()->stats_collector()->ResetDiscardedMemory();
}
PrepareForSweepVisitor(heap_.heap(), &space_states_, &empty_normal_pages_,
&empty_large_pages_,
config.compactable_space_handling)
.Run(heap_);
if (config.sweeping_type >= SweepingConfig::SweepingType::kIncremental) {
ScheduleLowPriorityIncrementalSweeping();
ScheduleIncrementalSweeping(kDelayWhileLowPrioritySweepingMakesProgress);
}
if (config.sweeping_type >=
SweepingConfig::SweepingType::kIncrementalAndConcurrent) {
ScheduleConcurrentSweeping();
}
}
void SweepForTask(v8::base::TimeDelta max_duration) {
if (low_priority_task_ran_) {
ScheduleIncrementalSweeping(kDelayWhileLowPrioritySweepingMakesProgress);
return;
}
switch (
SweepInForegroundTaskImpl(max_duration, StatsCollector::kSweepInTask)) {
case SweepResult::kFullyDone:
return;
case SweepResult::kInProgress:
ScheduleIncrementalSweeping(kDelayForRegularPrioritySweeping);
return;
case SweepResult::kMainThreadDoneConcurrentInProgress:
ScheduleIncrementalSweeping(kDelayWhileConcurrentSweepingMakesProgress);
return;
}
UNREACHABLE();
}
void SweepForLowPriorityTask(v8::base::TimeDelta max_duration) {
low_priority_task_ran_ = true;
switch (SweepInForegroundTaskImpl(
max_duration, StatsCollector::kSweepInLowPriorityTask)) {
case SweepResult::kFullyDone:
return;
case SweepResult::kInProgress:
ScheduleLowPriorityIncrementalSweeping();
return;
case SweepResult::kMainThreadDoneConcurrentInProgress:
ScheduleLowPriorityIncrementalSweeping(
kDelayWhileLowPrioritySweepingMakesProgress);
return;
}
UNREACHABLE();
}
bool SweepForLargeAllocation(BaseSpace* space, size_t size,
v8::base::TimeDelta max_duration) {
DCHECK(space->is_large());
#ifdef DEBUG
SweepingState& space_state = space_states_[space->index()];
DCHECK(space_state.unswept_pages.IsEmpty());
DCHECK(space_state.swept_unfinalized_pages.IsEmpty());
#endif
if (empty_large_pages_.swept_unfinalized_pages.IsEmpty() &&
empty_large_pages_.unswept_pages.IsEmpty()) {
return false;
}
StatsCollector::EnabledScope incremental_sweep_scope(
stats_collector_, StatsCollector::kIncrementalSweep);
StatsCollector::DisabledScope sweep_on_allocation_scope(
stats_collector_, StatsCollector::kSweepOnAllocation);
MutatorThreadSweepingScope sweeping_in_progress(*this);
const auto deadline = v8::base::TimeTicks::Now() + max_duration;
SweepFinalizer finalizer(
platform_, stats_collector_, space, &unused_destroyed_normal_pages_,
config_.free_memory_handling, EmptyPageHandling::kDestroy);
if (finalizer.FinalizeWithDeadlineAndSize(
StatsCollector::kSweepFinalizeEmptyPages, empty_large_pages_,
deadline, size)) {
return true;
}
MutatorThreadSweeper sweeper(heap_.heap(), platform_, stats_collector_,
space, &unused_destroyed_normal_pages_,
config_.free_memory_handling,
EmptyPageHandling::kDestroy);
if (sweeper.SweepWithDeadlineAndSize(StatsCollector::kSweepEmptyPages,
empty_large_pages_, deadline, size)) {
return true;
}
return false;
}
bool SweepForNormalAllocation(BaseSpace* space, size_t size,
v8::base::TimeDelta max_duration) {
DCHECK(!space->is_large());
if (unused_destroyed_normal_pages_ > 0 && page_pool_.pooled() > 0) {
unused_destroyed_normal_pages_--;
return false;
}
SweepingState& space_state = space_states_[space->index()];
if (empty_normal_pages_.swept_unfinalized_pages.IsEmpty() &&
empty_normal_pages_.unswept_pages.IsEmpty() &&
space_state.swept_unfinalized_pages.IsEmpty() &&
space_state.unswept_pages.IsEmpty()) {
return false;
}
StatsCollector::EnabledScope incremental_sweep_scope(
stats_collector_, StatsCollector::kIncrementalSweep);
StatsCollector::DisabledScope sweep_on_allocation_scope(
stats_collector_, StatsCollector::kSweepOnAllocation);
MutatorThreadSweepingScope sweeping_in_progress(*this);
const auto deadline = v8::base::TimeTicks::Now() + max_duration;
SweepFinalizer finalizer(
platform_, stats_collector_, space, &unused_destroyed_normal_pages_,
config_.free_memory_handling, EmptyPageHandling::kReturn);
if (finalizer.FinalizeWithDeadlineAndSize(
StatsCollector::kSweepFinalizeEmptyPages, empty_normal_pages_,
deadline, size)) {
return true;
}
MutatorThreadSweeper sweeper(heap_.heap(), platform_, stats_collector_,
space, &unused_destroyed_normal_pages_,
config_.free_memory_handling,
EmptyPageHandling::kReturn);
if (sweeper.SweepWithDeadlineAndSize(StatsCollector::kSweepEmptyPages,
empty_normal_pages_, deadline, size)) {
return true;
}
if (finalizer.FinalizeWithDeadlineAndSize(
StatsCollector::kSweepFinalizeSweptPages, space_state, deadline,
size)) {
return true;
}
if (sweeper.SweepWithDeadlineAndSize(StatsCollector::kSweepPages,
space_state, deadline, size)) {
return true;
}
return false;
}
bool SweepForAllocationIfRunning(BaseSpace* space, size_t size,
v8::base::TimeDelta max_duration) {
if (!is_in_progress_) {
return false;
}
if (is_sweeping_on_mutator_thread_) {
return false;
}
return space->is_large()
? SweepForLargeAllocation(space, size, max_duration)
: SweepForNormalAllocation(space, size, max_duration);
}
bool FinishIfRunning() {
if (!is_in_progress_) {
return false;
}
if (is_sweeping_on_mutator_thread_) {
return false;
}
{
std::optional<StatsCollector::EnabledScope> stats_scope;
if (config_.sweeping_type != SweepingConfig::SweepingType::kAtomic) {
stats_scope.emplace(stats_collector_,
StatsCollector::kIncrementalSweep);
}
StatsCollector::EnabledScope inner_scope(stats_collector_,
StatsCollector::kSweepFinish);
if (concurrent_sweeper_handle_ && concurrent_sweeper_handle_->IsValid() &&
concurrent_sweeper_handle_->UpdatePriorityEnabled()) {
concurrent_sweeper_handle_->UpdatePriority(kBackgroundBoostedPriority);
}
Finish();
}
NotifyDone();
return true;
}
bool IsConcurrentSweepingDone() const {
return !concurrent_sweeper_handle_ ||
!concurrent_sweeper_handle_->IsValid() ||
!concurrent_sweeper_handle_->IsActive();
}
void FinishIfOutOfWork() {
if (!is_in_progress_ || is_sweeping_on_mutator_thread_) {
return;
}
if (!concurrent_sweeper_handle_ || !concurrent_sweeper_handle_->IsValid() ||
concurrent_sweeper_handle_->IsActive()) {
return;
}
DCHECK(std::all_of(space_states_.begin(), space_states_.end(),
[](const SweepingState& state) {
return state.unswept_pages.IsEmpty();
}));
DCHECK(empty_normal_pages_.unswept_pages.IsEmpty());
DCHECK(empty_large_pages_.unswept_pages.IsEmpty());
if (std::any_of(space_states_.begin(), space_states_.end(),
[](const SweepingState& state) {
return !state.swept_unfinalized_pages.IsEmpty();
})) {
return;
}
if (!empty_normal_pages_.swept_unfinalized_pages.IsEmpty() ||
!empty_large_pages_.swept_unfinalized_pages.IsEmpty()) {
return;
}
{
StatsCollector::EnabledScope stats_scope(
stats_collector_, StatsCollector::kSweepFinishIfOutOfWork);
FinalizeSweep();
}
NotifyDone();
}
void Finish() {
DCHECK(is_in_progress_);
MutatorThreadSweepingScope sweeping_in_progress(*this);
SweepFinalizer finalizer(
platform_, stats_collector_, kSweepWithoutSpaceAssignment,
&unused_destroyed_normal_pages_, config_.free_memory_handling,
EmptyPageHandling::kDestroy);
finalizer.Finalize(space_states_);
finalizer.Finalize(empty_normal_pages_);
finalizer.Finalize(empty_large_pages_);
MutatorThreadSweeper sweeper(
heap_.heap(), platform_, stats_collector_, kSweepWithoutSpaceAssignment,
&unused_destroyed_normal_pages_, config_.free_memory_handling,
EmptyPageHandling::kDestroy);
sweeper.Sweep(space_states_);
sweeper.Sweep(empty_normal_pages_);
sweeper.Sweep(empty_large_pages_);
FinalizeSweep();
}
void FinalizeSweep() {
SynchronizeAndFinalizeConcurrentAndIncrementalSweeping();
space_states_.clear();
is_in_progress_ = false;
notify_done_pending_ = true;
unused_destroyed_normal_pages_ = 0;
}
void NotifyDone() {
DCHECK(!is_in_progress_);
DCHECK(notify_done_pending_);
notify_done_pending_ = false;
stats_collector_->NotifySweepingCompleted(config_.sweeping_type);
if (config_.free_memory_handling ==
FreeMemoryHandling::kDiscardWherePossible) {
heap_.heap()->page_backend()->ReleasePooledPages();
}
}
void WaitForConcurrentSweepingForTesting() {
if (concurrent_sweeper_handle_) concurrent_sweeper_handle_->Join();
}
bool IsSweepingOnMutatorThread() const {
return is_sweeping_on_mutator_thread_;
}
bool IsSweepingInProgress() const { return is_in_progress_; }
bool PerformSweepOnMutatorThread(v8::base::TimeDelta max_duration,
StatsCollector::ScopeId internal_scope_id,
MutatorThreadSweepingMode sweeping_mode) {
if (!is_in_progress_) return true;
MutatorThreadSweepingScope sweeping_in_progress(*this);
{
StatsCollector::EnabledScope stats_scope(
stats_collector_, StatsCollector::kIncrementalSweep);
MutatorThreadSweeper sweeper(
heap_.heap(), platform_, stats_collector_,
kSweepWithoutSpaceAssignment, &unused_destroyed_normal_pages_,
config_.free_memory_handling, EmptyPageHandling::kDestroy);
{
StatsCollector::EnabledScope inner_stats_scope(
stats_collector_, internal_scope_id, "max_duration_ms",
max_duration.InMillisecondsF(), "sweeping_mode",
ToString(sweeping_mode));
const auto deadline = v8::base::TimeTicks::Now() + max_duration;
if (!sweeper.FinalizeAndSweepWithDeadline(
StatsCollector::kSweepFinalizeEmptyPages, empty_normal_pages_,
deadline, sweeping_mode)) {
return false;
}
for (auto& state : space_states_) {
if (!sweeper.FinalizeAndSweepWithDeadline(
StatsCollector::kSweepFinalizeSweptPages, state, deadline,
sweeping_mode)) {
return false;
}
}
if (!sweeper.FinalizeAndSweepWithDeadline(
StatsCollector::kSweepFinalizeEmptyPages, empty_large_pages_,
deadline, sweeping_mode)) {
return false;
}
if (sweeping_mode != MutatorThreadSweepingMode::kAll) {
return false;
}
}
FinalizeSweep();
}
NotifyDone();
return true;
}
void AddMutatorThreadSweepingObserver(
Sweeper::SweepingOnMutatorThreadObserver* observer) {
DCHECK_EQ(mutator_thread_sweeping_observers_.end(),
std::find(mutator_thread_sweeping_observers_.begin(),
mutator_thread_sweeping_observers_.end(), observer));
mutator_thread_sweeping_observers_.push_back(observer);
}
void RemoveMutatorThreadSweepingObserver(
Sweeper::SweepingOnMutatorThreadObserver* observer) {
const auto it =
std::find(mutator_thread_sweeping_observers_.begin(),
mutator_thread_sweeping_observers_.end(), observer);
DCHECK_NE(mutator_thread_sweeping_observers_.end(), it);
mutator_thread_sweeping_observers_.erase(it);
}
private:
class MutatorThreadSweepingScope final {
public:
explicit MutatorThreadSweepingScope(SweeperImpl& sweeper)
: sweeper_(sweeper) {
DCHECK(!sweeper_.is_sweeping_on_mutator_thread_);
sweeper_.is_sweeping_on_mutator_thread_ = true;
for (auto* observer : sweeper_.mutator_thread_sweeping_observers_) {
observer->Start();
}
}
~MutatorThreadSweepingScope() {
sweeper_.is_sweeping_on_mutator_thread_ = false;
for (auto* observer : sweeper_.mutator_thread_sweeping_observers_) {
observer->End();
}
}
MutatorThreadSweepingScope(const MutatorThreadSweepingScope&) = delete;
MutatorThreadSweepingScope& operator=(const MutatorThreadSweepingScope&) =
delete;
private:
SweeperImpl& sweeper_;
};
class IncrementalSweepTask final : public cppgc::Task {
public:
using Handle = SingleThreadedHandle;
static constexpr auto kMaxSweepDuration =
v8::base::TimeDelta::FromMilliseconds(5);
IncrementalSweepTask(SweeperImpl& sweeper, cppgc::TaskPriority priority)
: sweeper_(sweeper),
handle_(Handle::NonEmptyTag{}),
priority_(priority) {}
static Handle Post(SweeperImpl& sweeper,
const std::shared_ptr<cppgc::TaskRunner>& runner,
cppgc::TaskPriority priority,
std::optional<v8::base::TimeDelta> delay = {}) {
auto task = std::make_unique<IncrementalSweepTask>(sweeper, priority);
auto handle = task->handle_;
if (delay.has_value()) {
runner->PostNonNestableDelayedTask(std::move(task),
delay->InSecondsF());
} else {
runner->PostNonNestableTask(std::move(task));
}
return handle;
}
void Run() override {
if (handle_.IsCanceled()) {
return;
}
switch (priority_) {
case kForegroundRegularPriority:
sweeper_.SweepForTask(kMaxSweepDuration);
return;
case kForegroundLowPriority:
sweeper_.SweepForLowPriorityTask(kMaxSweepDuration);
return;
default:
UNREACHABLE();
}
}
private:
SweeperImpl& sweeper_;
Handle handle_;
cppgc::TaskPriority priority_;
};
enum class SweepResult {
kFullyDone,
kInProgress,
kMainThreadDoneConcurrentInProgress,
};
static constexpr double kMaxHeapPercentageForNoSweeping = 50;
static constexpr auto kDelayWhileLowPrioritySweepingMakesProgress =
v8::base::TimeDelta::FromMilliseconds(100);
static constexpr auto kDelayWhileConcurrentSweepingMakesProgress =
v8::base::TimeDelta::FromMilliseconds(5);
static constexpr auto kDelayForRegularPrioritySweeping =
v8::base::TimeDelta::FromMilliseconds(1);
SweepResult SweepInForegroundTaskImpl(v8::base::TimeDelta max_duration,
StatsCollector::ScopeId scope) {
bool concurrent_sweep_complete = IsConcurrentSweepingDone();
const auto start = v8::base::TimeTicks::Now();
bool main_thread_sweep_complete = PerformSweepOnMutatorThread(
max_duration, scope,
concurrent_sweep_complete ? MutatorThreadSweepingMode::kAll
: MutatorThreadSweepingMode::kOnlyFinalizers);
if (main_thread_sweep_complete && !concurrent_sweep_complete &&
IsConcurrentSweepingDone()) {
max_duration -= (v8::base::TimeTicks::Now() - start);
if (max_duration > v8::base::TimeDelta::FromMilliseconds(0)) {
concurrent_sweep_complete = true;
main_thread_sweep_complete = PerformSweepOnMutatorThread(
max_duration, scope, MutatorThreadSweepingMode::kAll);
}
}
if (main_thread_sweep_complete) {
if (!concurrent_sweep_complete) {
return SweepResult::kMainThreadDoneConcurrentInProgress;
} else {
CHECK(!is_in_progress_);
return SweepResult::kFullyDone;
}
}
return SweepResult::kInProgress;
}
void ScheduleIncrementalSweeping(
std::optional<v8::base::TimeDelta> delay = {}) {
DCHECK_GE(config_.sweeping_type,
SweepingConfig::SweepingType::kIncremental);
if (!foreground_task_runner_) {
return;
}
low_priority_task_ran_ = false;
incremental_sweeper_handle_.CancelIfNonEmpty();
incremental_sweeper_handle_ = IncrementalSweepTask::Post(
*this, foreground_task_runner_, kForegroundRegularPriority, delay);
}
void ScheduleLowPriorityIncrementalSweeping(
std::optional<v8::base::TimeDelta> delay = {}) {
DCHECK_GE(config_.sweeping_type,
SweepingConfig::SweepingType::kIncremental);
if (!low_priority_foreground_task_runner_) {
return;
}
incremental_sweeper_low_priority_handle_.CancelIfNonEmpty();
incremental_sweeper_low_priority_handle_ =
IncrementalSweepTask::Post(*this, low_priority_foreground_task_runner_,
kForegroundLowPriority, delay);
}
void ScheduleConcurrentSweeping() {
DCHECK_GE(config_.sweeping_type,
SweepingConfig::SweepingType::kIncrementalAndConcurrent);
concurrent_sweeper_handle_ = platform_->PostJob(
kBackgroundRegularPriority,
std::make_unique<ConcurrentSweepTask>(
platform_, *heap_.heap(), &space_states_, &empty_normal_pages_,
&empty_large_pages_, config_.free_memory_handling));
}
void CancelAllSweepingTasks() {
if (incremental_sweeper_handle_) {
incremental_sweeper_handle_.Cancel();
}
if (incremental_sweeper_low_priority_handle_) {
incremental_sweeper_low_priority_handle_.Cancel();
}
if (concurrent_sweeper_handle_ && concurrent_sweeper_handle_->IsValid()) {
concurrent_sweeper_handle_->Cancel();
}
}
void SynchronizeAndFinalizeConcurrentAndIncrementalSweeping() {
CancelAllSweepingTasks();
DCHECK(std::all_of(space_states_.begin(), space_states_.end(),
[](const SweepingState& state) {
return state.unswept_pages.IsEmpty();
}));
DCHECK(empty_normal_pages_.unswept_pages.IsEmpty());
DCHECK(empty_large_pages_.unswept_pages.IsEmpty());
SweepFinalizer finalizer(
platform_, stats_collector_, kSweepWithoutSpaceAssignment,
&unused_destroyed_normal_pages_, config_.free_memory_handling,
EmptyPageHandling::kDestroy);
finalizer.Finalize(space_states_);
finalizer.Finalize(empty_normal_pages_);
finalizer.Finalize(empty_large_pages_);
}
RawHeap& heap_;
NormalPageMemoryPool& page_pool_;
StatsCollector* const stats_collector_;
SpaceStates space_states_;
SweepingState empty_normal_pages_;
SweepingState empty_large_pages_;
size_t unused_destroyed_normal_pages_ = 0;
cppgc::Platform* platform_;
std::shared_ptr<cppgc::TaskRunner> foreground_task_runner_;
std::shared_ptr<cppgc::TaskRunner> low_priority_foreground_task_runner_;
SweepingConfig config_;
IncrementalSweepTask::Handle incremental_sweeper_handle_;
IncrementalSweepTask::Handle incremental_sweeper_low_priority_handle_;
std::unique_ptr<cppgc::JobHandle> concurrent_sweeper_handle_;
std::vector<Sweeper::SweepingOnMutatorThreadObserver*>
mutator_thread_sweeping_observers_;
bool low_priority_task_ran_ = false;
bool is_in_progress_ = false;
bool notify_done_pending_ = false;
bool is_sweeping_on_mutator_thread_ = false;
};
Sweeper::Sweeper(HeapBase& heap)
: heap_(heap), impl_(std::make_unique<SweeperImpl>(heap)) {}
Sweeper::~Sweeper() = default;
void Sweeper::Start(SweepingConfig config) { impl_->Start(config); }
bool Sweeper::FinishIfRunning() { return impl_->FinishIfRunning(); }
void Sweeper::FinishIfOutOfWork() { impl_->FinishIfOutOfWork(); }
void Sweeper::WaitForConcurrentSweepingForTesting() {
impl_->WaitForConcurrentSweepingForTesting();
}
bool Sweeper::SweepForAllocationIfRunning(BaseSpace* space, size_t size,
v8::base::TimeDelta max_duration) {
return impl_->SweepForAllocationIfRunning(space, size, max_duration);
}
bool Sweeper::IsSweepingOnMutatorThread() const {
return impl_->IsSweepingOnMutatorThread();
}
bool Sweeper::IsSweepingInProgress() const {
return impl_->IsSweepingInProgress();
}
bool Sweeper::PerformSweepOnMutatorThread(v8::base::TimeDelta max_duration,
StatsCollector::ScopeId scope_id) {
return impl_->PerformSweepOnMutatorThread(max_duration, scope_id,
MutatorThreadSweepingMode::kAll);
}
Sweeper::SweepingOnMutatorThreadObserver::SweepingOnMutatorThreadObserver(
Sweeper& sweeper)
: sweeper_(sweeper) {
sweeper_.impl_->AddMutatorThreadSweepingObserver(this);
}
Sweeper::SweepingOnMutatorThreadObserver::~SweepingOnMutatorThreadObserver() {
sweeper_.impl_->RemoveMutatorThreadSweepingObserver(this);
}
}