| // 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/sweeper.h" |
| |
| #include <atomic> |
| #include <memory> |
| #include <vector> |
| |
| #include "include/cppgc/platform.h" |
| #include "src/base/optional.h" |
| #include "src/base/platform/mutex.h" |
| #include "src/heap/cppgc/free-list.h" |
| #include "src/heap/cppgc/globals.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/object-start-bitmap.h" |
| #include "src/heap/cppgc/raw-heap.h" |
| #include "src/heap/cppgc/sanitizers.h" |
| #include "src/heap/cppgc/stats-collector.h" |
| #include "src/heap/cppgc/task-handle.h" |
| |
| namespace cppgc { |
| namespace internal { |
| |
| namespace { |
| |
| using v8::base::Optional; |
| |
| class ObjectStartBitmapVerifier |
| : private HeapVisitor<ObjectStartBitmapVerifier> { |
| friend class HeapVisitor<ObjectStartBitmapVerifier>; |
| |
| public: |
| void Verify(RawHeap* heap) { Traverse(heap); } |
| |
| private: |
| bool VisitNormalPage(NormalPage* page) { |
| // Remember bitmap and reset previous pointer. |
| 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(raw_header)); |
| if (prev_) { |
| CHECK_EQ(prev_, bitmap_->FindHeader(raw_header - 1)); |
| } |
| prev_ = header; |
| return true; |
| } |
| |
| PlatformAwareObjectStartBitmap* bitmap_ = nullptr; |
| HeapObjectHeader* prev_ = nullptr; |
| }; |
| |
| template <typename T> |
| class ThreadSafeStack { |
| public: |
| ThreadSafeStack() = default; |
| |
| void Push(T t) { |
| v8::base::LockGuard<v8::base::Mutex> lock(&mutex_); |
| vector_.push_back(std::move(t)); |
| } |
| |
| Optional<T> Pop() { |
| v8::base::LockGuard<v8::base::Mutex> lock(&mutex_); |
| if (vector_.empty()) return v8::base::nullopt; |
| T top = std::move(vector_.back()); |
| vector_.pop_back(); |
| // std::move is redundant but is needed to avoid the bug in gcc-7. |
| return std::move(top); |
| } |
| |
| template <typename It> |
| void Insert(It begin, It end) { |
| v8::base::LockGuard<v8::base::Mutex> lock(&mutex_); |
| vector_.insert(vector_.end(), begin, end); |
| } |
| |
| bool IsEmpty() const { |
| v8::base::LockGuard<v8::base::Mutex> lock(&mutex_); |
| return vector_.empty(); |
| } |
| |
| private: |
| std::vector<T> vector_; |
| mutable v8::base::Mutex mutex_; |
| }; |
| |
| struct SpaceState { |
| struct SweptPageState { |
| BasePage* page = nullptr; |
| std::vector<HeapObjectHeader*> unfinalized_objects; |
| FreeList cached_free_list; |
| std::vector<FreeList::Block> unfinalized_free_list; |
| bool is_empty = false; |
| }; |
| |
| ThreadSafeStack<BasePage*> unswept_pages; |
| ThreadSafeStack<SweptPageState> swept_unfinalized_pages; |
| }; |
| |
| using SpaceStates = std::vector<SpaceState>; |
| |
| void StickyUnmark(HeapObjectHeader* header) { |
| // Young generation in Oilpan uses sticky mark bits. |
| #if !defined(CPPGC_YOUNG_GENERATION) |
| header->Unmark<AccessMode::kAtomic>(); |
| #endif |
| } |
| |
| // Builder that finalizes objects and adds freelist entries right away. |
| class InlinedFinalizationBuilder final { |
| public: |
| using ResultType = bool; |
| |
| explicit InlinedFinalizationBuilder(BasePage* page) : page_(page) {} |
| |
| void AddFinalizer(HeapObjectHeader* header, size_t size) { |
| header->Finalize(); |
| SET_MEMORY_INACCESSIBLE(header, size); |
| } |
| |
| void AddFreeListEntry(Address start, size_t size) { |
| auto* space = NormalPageSpace::From(page_->space()); |
| space->free_list().Add({start, size}); |
| } |
| |
| ResultType GetResult(bool is_empty) { return is_empty; } |
| |
| private: |
| BasePage* page_; |
| }; |
| |
| // Builder that produces results for deferred processing. |
| class DeferredFinalizationBuilder final { |
| public: |
| using ResultType = SpaceState::SweptPageState; |
| |
| explicit DeferredFinalizationBuilder(BasePage* page) { result_.page = page; } |
| |
| void AddFinalizer(HeapObjectHeader* header, size_t size) { |
| if (header->IsFinalizable()) { |
| result_.unfinalized_objects.push_back({header}); |
| found_finalizer_ = true; |
| } else { |
| SET_MEMORY_INACCESSIBLE(header, size); |
| } |
| } |
| |
| void AddFreeListEntry(Address start, size_t size) { |
| if (found_finalizer_) { |
| result_.unfinalized_free_list.push_back({start, size}); |
| } else { |
| result_.cached_free_list.Add({start, size}); |
| } |
| found_finalizer_ = false; |
| } |
| |
| ResultType&& GetResult(bool is_empty) { |
| result_.is_empty = is_empty; |
| return std::move(result_); |
| } |
| |
| private: |
| ResultType result_; |
| bool found_finalizer_ = false; |
| }; |
| |
| template <typename FinalizationBuilder> |
| typename FinalizationBuilder::ResultType SweepNormalPage(NormalPage* page) { |
| constexpr auto kAtomicAccess = AccessMode::kAtomic; |
| FinalizationBuilder builder(page); |
| |
| PlatformAwareObjectStartBitmap& bitmap = page->object_start_bitmap(); |
| bitmap.Clear(); |
| |
| Address start_of_gap = page->PayloadStart(); |
| for (Address begin = page->PayloadStart(), end = page->PayloadEnd(); |
| begin != end;) { |
| HeapObjectHeader* header = reinterpret_cast<HeapObjectHeader*>(begin); |
| const size_t size = header->GetSize(); |
| // Check if this is a free list entry. |
| if (header->IsFree<kAtomicAccess>()) { |
| SET_MEMORY_INACCESSIBLE(header, std::min(kFreeListEntrySize, size)); |
| begin += size; |
| continue; |
| } |
| // Check if object is not marked (not reachable). |
| if (!header->IsMarked<kAtomicAccess>()) { |
| builder.AddFinalizer(header, size); |
| begin += size; |
| continue; |
| } |
| // The object is alive. |
| const Address header_address = reinterpret_cast<Address>(header); |
| if (start_of_gap != header_address) { |
| builder.AddFreeListEntry( |
| start_of_gap, static_cast<size_t>(header_address - start_of_gap)); |
| bitmap.SetBit(start_of_gap); |
| } |
| StickyUnmark(header); |
| bitmap.SetBit(begin); |
| begin += size; |
| start_of_gap = begin; |
| } |
| |
| if (start_of_gap != page->PayloadStart() && |
| start_of_gap != page->PayloadEnd()) { |
| builder.AddFreeListEntry( |
| start_of_gap, static_cast<size_t>(page->PayloadEnd() - start_of_gap)); |
| bitmap.SetBit(start_of_gap); |
| } |
| |
| const bool is_empty = (start_of_gap == page->PayloadStart()); |
| return builder.GetResult(is_empty); |
| } |
| |
| // SweepFinalizer is responsible for heap/space/page finalization. Finalization |
| // is defined as a step following concurrent sweeping which: |
| // - calls finalizers; |
| // - returns (unmaps) empty pages; |
| // - merges freelists to the space's freelist. |
| class SweepFinalizer final { |
| public: |
| explicit SweepFinalizer(cppgc::Platform* platform) : platform_(platform) {} |
| |
| void FinalizeHeap(SpaceStates* space_states) { |
| for (SpaceState& space_state : *space_states) { |
| FinalizeSpace(&space_state); |
| } |
| } |
| |
| void FinalizeSpace(SpaceState* space_state) { |
| while (auto page_state = space_state->swept_unfinalized_pages.Pop()) { |
| FinalizePage(&*page_state); |
| } |
| } |
| |
| bool FinalizeSpaceWithDeadline(SpaceState* space_state, |
| double deadline_in_seconds) { |
| DCHECK(platform_); |
| static constexpr size_t kDeadlineCheckInterval = 8; |
| size_t page_count = 1; |
| |
| while (auto page_state = space_state->swept_unfinalized_pages.Pop()) { |
| FinalizePage(&*page_state); |
| |
| if (page_count % kDeadlineCheckInterval == 0 && |
| deadline_in_seconds <= platform_->MonotonicallyIncreasingTime()) { |
| return false; |
| } |
| |
| page_count++; |
| } |
| |
| return true; |
| } |
| |
| void FinalizePage(SpaceState::SweptPageState* page_state) { |
| DCHECK(page_state); |
| DCHECK(page_state->page); |
| BasePage* page = page_state->page; |
| |
| // Call finalizers. |
| for (HeapObjectHeader* object : page_state->unfinalized_objects) { |
| const size_t size = object->GetSize(); |
| object->Finalize(); |
| SET_MEMORY_INACCESSIBLE(object, size); |
| } |
| |
| // Unmap page if empty. |
| if (page_state->is_empty) { |
| BasePage::Destroy(page); |
| return; |
| } |
| |
| DCHECK(!page->is_large()); |
| |
| // Merge freelists without finalizers. |
| FreeList& space_freelist = |
| NormalPageSpace::From(page->space())->free_list(); |
| space_freelist.Append(std::move(page_state->cached_free_list)); |
| |
| // Merge freelist with finalizers. |
| for (auto entry : page_state->unfinalized_free_list) { |
| space_freelist.Add(std::move(entry)); |
| } |
| |
| // Add the page to the space. |
| page->space()->AddPage(page); |
| } |
| |
| private: |
| cppgc::Platform* platform_; |
| }; |
| |
| class MutatorThreadSweeper final : private HeapVisitor<MutatorThreadSweeper> { |
| friend class HeapVisitor<MutatorThreadSweeper>; |
| |
| public: |
| explicit MutatorThreadSweeper(SpaceStates* states, cppgc::Platform* platform) |
| : states_(states), platform_(platform) {} |
| |
| void Sweep() { |
| for (SpaceState& state : *states_) { |
| while (auto page = state.unswept_pages.Pop()) { |
| Traverse(*page); |
| } |
| } |
| } |
| |
| bool SweepWithDeadline(double deadline_in_seconds) { |
| DCHECK(platform_); |
| static constexpr double kSlackInSeconds = 0.001; |
| for (SpaceState& state : *states_) { |
| // FinalizeSpaceWithDeadline() and SweepSpaceWithDeadline() won't check |
| // the deadline until it sweeps 10 pages. So we give a small slack for |
| // safety. |
| const double remaining_budget = deadline_in_seconds - kSlackInSeconds - |
| platform_->MonotonicallyIncreasingTime(); |
| if (remaining_budget <= 0.) return false; |
| |
| // First, prioritize finalization of pages that were swept concurrently. |
| SweepFinalizer finalizer(platform_); |
| if (!finalizer.FinalizeSpaceWithDeadline(&state, deadline_in_seconds)) { |
| return false; |
| } |
| |
| // Help out the concurrent sweeper. |
| if (!SweepSpaceWithDeadline(&state, deadline_in_seconds)) { |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| private: |
| bool SweepSpaceWithDeadline(SpaceState* state, double deadline_in_seconds) { |
| static constexpr size_t kDeadlineCheckInterval = 8; |
| size_t page_count = 1; |
| while (auto page = state->unswept_pages.Pop()) { |
| Traverse(*page); |
| if (page_count % kDeadlineCheckInterval == 0 && |
| deadline_in_seconds <= platform_->MonotonicallyIncreasingTime()) { |
| return false; |
| } |
| page_count++; |
| } |
| |
| return true; |
| } |
| |
| bool VisitNormalPage(NormalPage* page) { |
| const bool is_empty = SweepNormalPage<InlinedFinalizationBuilder>(page); |
| if (is_empty) { |
| NormalPage::Destroy(page); |
| } else { |
| page->space()->AddPage(page); |
| } |
| return true; |
| } |
| |
| bool VisitLargePage(LargePage* page) { |
| HeapObjectHeader* header = page->ObjectHeader(); |
| if (header->IsMarked()) { |
| StickyUnmark(header); |
| page->space()->AddPage(page); |
| } else { |
| header->Finalize(); |
| LargePage::Destroy(page); |
| } |
| return true; |
| } |
| |
| SpaceStates* states_; |
| cppgc::Platform* platform_; |
| }; |
| |
| class ConcurrentSweepTask final : public cppgc::JobTask, |
| private HeapVisitor<ConcurrentSweepTask> { |
| friend class HeapVisitor<ConcurrentSweepTask>; |
| |
| public: |
| explicit ConcurrentSweepTask(SpaceStates* states) : states_(states) {} |
| |
| void Run(cppgc::JobDelegate* delegate) final { |
| for (SpaceState& state : *states_) { |
| while (auto page = state.unswept_pages.Pop()) { |
| Traverse(*page); |
| if (delegate->ShouldYield()) return; |
| } |
| } |
| is_completed_.store(true, std::memory_order_relaxed); |
| } |
| |
| size_t GetMaxConcurrency(size_t /* active_worker_count */) const final { |
| return is_completed_.load(std::memory_order_relaxed) ? 0 : 1; |
| } |
| |
| private: |
| bool VisitNormalPage(NormalPage* page) { |
| SpaceState::SweptPageState sweep_result = |
| SweepNormalPage<DeferredFinalizationBuilder>(page); |
| const size_t space_index = page->space()->index(); |
| DCHECK_GT(states_->size(), space_index); |
| SpaceState& space_state = (*states_)[space_index]; |
| space_state.swept_unfinalized_pages.Push(std::move(sweep_result)); |
| return true; |
| } |
| |
| bool VisitLargePage(LargePage* page) { |
| HeapObjectHeader* header = page->ObjectHeader(); |
| if (header->IsMarked()) { |
| StickyUnmark(header); |
| page->space()->AddPage(page); |
| return true; |
| } |
| if (!header->IsFinalizable()) { |
| LargePage::Destroy(page); |
| return true; |
| } |
| const size_t space_index = page->space()->index(); |
| DCHECK_GT(states_->size(), space_index); |
| SpaceState& state = (*states_)[space_index]; |
| state.swept_unfinalized_pages.Push( |
| {page, {page->ObjectHeader()}, {}, {}, true}); |
| return true; |
| } |
| |
| SpaceStates* states_; |
| std::atomic_bool is_completed_{false}; |
| }; |
| |
| // This visitor: |
| // - resets linear allocation buffers and clears free lists for all spaces; |
| // - moves all Heap pages to local Sweeper's state (SpaceStates). |
| class PrepareForSweepVisitor final |
| : public HeapVisitor<PrepareForSweepVisitor> { |
| using CompactableSpaceHandling = |
| Sweeper::SweepingConfig::CompactableSpaceHandling; |
| |
| public: |
| PrepareForSweepVisitor(SpaceStates* states, |
| CompactableSpaceHandling compactable_space_handling) |
| : states_(states), |
| compactable_space_handling_(compactable_space_handling) {} |
| |
| bool VisitNormalPageSpace(NormalPageSpace* space) { |
| if ((compactable_space_handling_ == CompactableSpaceHandling::kIgnore) && |
| space->is_compactable()) |
| return true; |
| DCHECK(!space->linear_allocation_buffer().size()); |
| space->free_list().Clear(); |
| ExtractPages(space); |
| return true; |
| } |
| |
| bool VisitLargePageSpace(LargePageSpace* space) { |
| ExtractPages(space); |
| return true; |
| } |
| |
| private: |
| void ExtractPages(BaseSpace* space) { |
| BaseSpace::Pages space_pages = space->RemoveAllPages(); |
| (*states_)[space->index()].unswept_pages.Insert(space_pages.begin(), |
| space_pages.end()); |
| } |
| |
| SpaceStates* states_; |
| CompactableSpaceHandling compactable_space_handling_; |
| }; |
| |
| } // namespace |
| |
| class Sweeper::SweeperImpl final { |
| public: |
| SweeperImpl(RawHeap* heap, cppgc::Platform* platform, |
| StatsCollector* stats_collector) |
| : heap_(heap), |
| stats_collector_(stats_collector), |
| space_states_(heap->size()), |
| platform_(platform), |
| foreground_task_runner_(platform_->GetForegroundTaskRunner()) {} |
| |
| ~SweeperImpl() { CancelSweepers(); } |
| |
| void Start(SweepingConfig config) { |
| is_in_progress_ = true; |
| #if DEBUG |
| // Verify bitmap for all spaces regardless of |compactable_space_handling|. |
| ObjectStartBitmapVerifier().Verify(heap_); |
| #endif |
| PrepareForSweepVisitor(&space_states_, config.compactable_space_handling) |
| .Traverse(heap_); |
| |
| if (config.sweeping_type == SweepingConfig::SweepingType::kAtomic) { |
| Finish(); |
| } else { |
| DCHECK_EQ(SweepingConfig::SweepingType::kIncrementalAndConcurrent, |
| config.sweeping_type); |
| ScheduleIncrementalSweeping(); |
| ScheduleConcurrentSweeping(); |
| } |
| } |
| |
| void FinishIfRunning() { |
| if (!is_in_progress_) return; |
| |
| if (concurrent_sweeper_handle_ && concurrent_sweeper_handle_->IsValid() && |
| concurrent_sweeper_handle_->UpdatePriorityEnabled()) { |
| concurrent_sweeper_handle_->UpdatePriority( |
| cppgc::TaskPriority::kUserBlocking); |
| } |
| Finish(); |
| } |
| |
| void Finish() { |
| DCHECK(is_in_progress_); |
| |
| // First, call finalizers on the mutator thread. |
| SweepFinalizer finalizer(platform_); |
| finalizer.FinalizeHeap(&space_states_); |
| |
| // Then, help out the concurrent thread. |
| MutatorThreadSweeper sweeper(&space_states_, platform_); |
| sweeper.Sweep(); |
| |
| // Synchronize with the concurrent sweeper and call remaining finalizers. |
| SynchronizeAndFinalizeConcurrentSweeping(); |
| |
| is_in_progress_ = false; |
| |
| stats_collector_->NotifySweepingCompleted(); |
| } |
| |
| void WaitForConcurrentSweepingForTesting() { |
| if (concurrent_sweeper_handle_) concurrent_sweeper_handle_->Join(); |
| } |
| |
| private: |
| class IncrementalSweepTask : public cppgc::IdleTask { |
| public: |
| using Handle = SingleThreadedHandle; |
| |
| explicit IncrementalSweepTask(SweeperImpl* sweeper) |
| : sweeper_(sweeper), handle_(Handle::NonEmptyTag{}) {} |
| |
| static Handle Post(SweeperImpl* sweeper, cppgc::TaskRunner* runner) { |
| auto task = std::make_unique<IncrementalSweepTask>(sweeper); |
| auto handle = task->GetHandle(); |
| runner->PostIdleTask(std::move(task)); |
| return handle; |
| } |
| |
| private: |
| void Run(double deadline_in_seconds) override { |
| if (handle_.IsCanceled() || !sweeper_->is_in_progress_) return; |
| |
| MutatorThreadSweeper sweeper(&sweeper_->space_states_, |
| sweeper_->platform_); |
| const bool sweep_complete = |
| sweeper.SweepWithDeadline(deadline_in_seconds); |
| |
| if (sweep_complete) { |
| sweeper_->SynchronizeAndFinalizeConcurrentSweeping(); |
| } else { |
| sweeper_->ScheduleIncrementalSweeping(); |
| } |
| } |
| |
| Handle GetHandle() const { return handle_; } |
| |
| SweeperImpl* sweeper_; |
| // TODO(chromium:1056170): Change to CancelableTask. |
| Handle handle_; |
| }; |
| |
| void ScheduleIncrementalSweeping() { |
| DCHECK(platform_); |
| if (!foreground_task_runner_ || |
| !foreground_task_runner_->IdleTasksEnabled()) |
| return; |
| |
| incremental_sweeper_handle_ = |
| IncrementalSweepTask::Post(this, foreground_task_runner_.get()); |
| } |
| |
| void ScheduleConcurrentSweeping() { |
| DCHECK(platform_); |
| |
| concurrent_sweeper_handle_ = platform_->PostJob( |
| cppgc::TaskPriority::kUserVisible, |
| std::make_unique<ConcurrentSweepTask>(&space_states_)); |
| } |
| |
| void CancelSweepers() { |
| if (incremental_sweeper_handle_) incremental_sweeper_handle_.Cancel(); |
| if (concurrent_sweeper_handle_ && concurrent_sweeper_handle_->IsValid()) |
| concurrent_sweeper_handle_->Cancel(); |
| } |
| |
| void SynchronizeAndFinalizeConcurrentSweeping() { |
| CancelSweepers(); |
| |
| SweepFinalizer finalizer(platform_); |
| finalizer.FinalizeHeap(&space_states_); |
| } |
| |
| RawHeap* heap_; |
| StatsCollector* stats_collector_; |
| SpaceStates space_states_; |
| cppgc::Platform* platform_; |
| std::shared_ptr<cppgc::TaskRunner> foreground_task_runner_; |
| IncrementalSweepTask::Handle incremental_sweeper_handle_; |
| std::unique_ptr<cppgc::JobHandle> concurrent_sweeper_handle_; |
| bool is_in_progress_ = false; |
| }; |
| |
| Sweeper::Sweeper(RawHeap* heap, cppgc::Platform* platform, |
| StatsCollector* stats_collector) |
| : impl_(std::make_unique<SweeperImpl>(heap, platform, stats_collector)) {} |
| |
| Sweeper::~Sweeper() = default; |
| |
| void Sweeper::Start(SweepingConfig config) { impl_->Start(config); } |
| void Sweeper::FinishIfRunning() { impl_->FinishIfRunning(); } |
| void Sweeper::WaitForConcurrentSweepingForTesting() { |
| impl_->WaitForConcurrentSweepingForTesting(); |
| } |
| |
| } // namespace internal |
| } // namespace cppgc |