// Copyright 2015 the V8 project authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "src/heap/array-buffer-tracker.h" #include <vector> #include "src/heap/array-buffer-collector.h" #include "src/heap/array-buffer-tracker-inl.h" #include "src/heap/heap.h" #include "src/heap/spaces.h" namespace v8 { namespace internal { LocalArrayBufferTracker::~LocalArrayBufferTracker() { CHECK(array_buffers_.empty()); } template <typename Callback> void LocalArrayBufferTracker::Process(Callback callback) { std::vector<JSArrayBuffer::Allocation> backing_stores_to_free; TrackingData kept_array_buffers; JSArrayBuffer new_buffer; JSArrayBuffer old_buffer; size_t freed_memory = 0; for (TrackingData::iterator it = array_buffers_.begin(); it != array_buffers_.end(); ++it) { old_buffer = it->first; DCHECK_EQ(page_, Page::FromHeapObject(old_buffer)); const CallbackResult result = callback(old_buffer, &new_buffer); if (result == kKeepEntry) { kept_array_buffers.insert(*it); } else if (result == kUpdateEntry) { DCHECK(!new_buffer.is_null()); Page* target_page = Page::FromHeapObject(new_buffer); { base::MutexGuard guard(target_page->mutex()); LocalArrayBufferTracker* tracker = target_page->local_tracker(); if (tracker == nullptr) { target_page->AllocateLocalTracker(); tracker = target_page->local_tracker(); } DCHECK_NOT_NULL(tracker); const size_t length = it->second.length; // We should decrement before adding to avoid potential overflows in // the external memory counters. DCHECK_EQ(it->first->is_wasm_memory(), it->second.is_wasm_memory); tracker->AddInternal(new_buffer, length); MemoryChunk::MoveExternalBackingStoreBytes( ExternalBackingStoreType::kArrayBuffer, static_cast<MemoryChunk*>(page_), static_cast<MemoryChunk*>(target_page), length); } } else if (result == kRemoveEntry) { freed_memory += it->second.length; // We pass backing_store() and stored length to the collector for freeing // the backing store. Wasm allocations will go through their own tracker // based on the backing store. backing_stores_to_free.push_back(it->second); } else { UNREACHABLE(); } } if (freed_memory) { page_->DecrementExternalBackingStoreBytes( ExternalBackingStoreType::kArrayBuffer, freed_memory); // TODO(wez): Remove backing-store from external memory accounting. page_->heap()->update_external_memory_concurrently_freed( static_cast<intptr_t>(freed_memory)); } array_buffers_.swap(kept_array_buffers); // Pass the backing stores that need to be freed to the main thread for // potential later distribution. page_->heap()->array_buffer_collector()->QueueOrFreeGarbageAllocations( std::move(backing_stores_to_free)); } void ArrayBufferTracker::PrepareToFreeDeadInNewSpace(Heap* heap) { DCHECK_EQ(heap->gc_state(), Heap::HeapState::SCAVENGE); for (Page* page : PageRange(heap->new_space()->from_space().first_page(), nullptr)) { bool empty = ProcessBuffers(page, kUpdateForwardedRemoveOthers); CHECK(empty); } } void ArrayBufferTracker::FreeAll(Page* page) { LocalArrayBufferTracker* tracker = page->local_tracker(); if (tracker == nullptr) return; tracker->Free([](JSArrayBuffer buffer) { return true; }); if (tracker->IsEmpty()) { page->ReleaseLocalTracker(); } } bool ArrayBufferTracker::ProcessBuffers(Page* page, ProcessingMode mode) { LocalArrayBufferTracker* tracker = page->local_tracker(); if (tracker == nullptr) return true; DCHECK(page->SweepingDone()); tracker->Process([mode](JSArrayBuffer old_buffer, JSArrayBuffer* new_buffer) { MapWord map_word = old_buffer->map_word(); if (map_word.IsForwardingAddress()) { *new_buffer = JSArrayBuffer::cast(map_word.ToForwardingAddress()); return LocalArrayBufferTracker::kUpdateEntry; } return mode == kUpdateForwardedKeepOthers ? LocalArrayBufferTracker::kKeepEntry : LocalArrayBufferTracker::kRemoveEntry; }); return tracker->IsEmpty(); } bool ArrayBufferTracker::IsTracked(JSArrayBuffer buffer) { Page* page = Page::FromHeapObject(buffer); { base::MutexGuard guard(page->mutex()); LocalArrayBufferTracker* tracker = page->local_tracker(); if (tracker == nullptr) return false; return tracker->IsTracked(buffer); } } void ArrayBufferTracker::TearDown(Heap* heap) { // ArrayBuffers can only be found in NEW_SPACE and OLD_SPACE. for (Page* p : *heap->old_space()) { FreeAll(p); } NewSpace* new_space = heap->new_space(); if (new_space->to_space().is_committed()) { for (Page* p : new_space->to_space()) { FreeAll(p); } } #ifdef DEBUG if (new_space->from_space().is_committed()) { for (Page* p : new_space->from_space()) { DCHECK(!p->contains_array_buffers()); } } #endif // DEBUG } } // namespace internal } // namespace v8