|  | // 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/free-list.h" | 
|  |  | 
|  | #include "src/base/macros.h" | 
|  | #include "src/common/globals.h" | 
|  | #include "src/heap/free-list-inl.h" | 
|  | #include "src/heap/heap.h" | 
|  | #include "src/heap/memory-chunk-inl.h" | 
|  | #include "src/objects/free-space-inl.h" | 
|  |  | 
|  | namespace v8 { | 
|  | namespace internal { | 
|  |  | 
|  | // ----------------------------------------------------------------------------- | 
|  | // Free lists for old object spaces implementation | 
|  |  | 
|  | void FreeListCategory::Reset(FreeList* owner) { | 
|  | if (is_linked(owner) && !top().is_null()) { | 
|  | owner->DecreaseAvailableBytes(available_); | 
|  | } | 
|  | set_top(FreeSpace()); | 
|  | set_prev(nullptr); | 
|  | set_next(nullptr); | 
|  | available_ = 0; | 
|  | } | 
|  |  | 
|  | FreeSpace FreeListCategory::PickNodeFromList(size_t minimum_size, | 
|  | size_t* node_size) { | 
|  | FreeSpace node = top(); | 
|  | DCHECK(!node.is_null()); | 
|  | DCHECK(Page::FromHeapObject(node)->CanAllocate()); | 
|  | if (static_cast<size_t>(node.Size()) < minimum_size) { | 
|  | *node_size = 0; | 
|  | return FreeSpace(); | 
|  | } | 
|  | set_top(node.next()); | 
|  | *node_size = node.Size(); | 
|  | UpdateCountersAfterAllocation(*node_size); | 
|  | return node; | 
|  | } | 
|  |  | 
|  | FreeSpace FreeListCategory::SearchForNodeInList(size_t minimum_size, | 
|  | size_t* node_size) { | 
|  | FreeSpace prev_non_evac_node; | 
|  | for (FreeSpace cur_node = top(); !cur_node.is_null(); | 
|  | cur_node = cur_node.next()) { | 
|  | DCHECK(Page::FromHeapObject(cur_node)->CanAllocate()); | 
|  | size_t size = cur_node.size(); | 
|  | if (size >= minimum_size) { | 
|  | DCHECK_GE(available_, size); | 
|  | UpdateCountersAfterAllocation(size); | 
|  | if (cur_node == top()) { | 
|  | set_top(cur_node.next()); | 
|  | } | 
|  | if (!prev_non_evac_node.is_null()) { | 
|  | MemoryChunk* chunk = MemoryChunk::FromHeapObject(prev_non_evac_node); | 
|  | if (chunk->owner_identity() == CODE_SPACE) { | 
|  | chunk->heap()->UnprotectAndRegisterMemoryChunk(chunk); | 
|  | } | 
|  | prev_non_evac_node.set_next(cur_node.next()); | 
|  | } | 
|  | *node_size = size; | 
|  | return cur_node; | 
|  | } | 
|  |  | 
|  | prev_non_evac_node = cur_node; | 
|  | } | 
|  | return FreeSpace(); | 
|  | } | 
|  |  | 
|  | void FreeListCategory::Free(Address start, size_t size_in_bytes, FreeMode mode, | 
|  | FreeList* owner) { | 
|  | FreeSpace free_space = FreeSpace::cast(HeapObject::FromAddress(start)); | 
|  | free_space.set_next(top()); | 
|  | set_top(free_space); | 
|  | available_ += size_in_bytes; | 
|  | if (mode == kLinkCategory) { | 
|  | if (is_linked(owner)) { | 
|  | owner->IncreaseAvailableBytes(size_in_bytes); | 
|  | } else { | 
|  | owner->AddCategory(this); | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | void FreeListCategory::RepairFreeList(Heap* heap) { | 
|  | Map free_space_map = ReadOnlyRoots(heap).free_space_map(); | 
|  | FreeSpace n = top(); | 
|  | while (!n.is_null()) { | 
|  | ObjectSlot map_slot = n.map_slot(); | 
|  | if (map_slot.contains_value(kNullAddress)) { | 
|  | map_slot.store(free_space_map); | 
|  | } else { | 
|  | DCHECK(map_slot.contains_value(free_space_map.ptr())); | 
|  | } | 
|  | n = n.next(); | 
|  | } | 
|  | } | 
|  |  | 
|  | void FreeListCategory::Relink(FreeList* owner) { | 
|  | DCHECK(!is_linked(owner)); | 
|  | owner->AddCategory(this); | 
|  | } | 
|  |  | 
|  | // ------------------------------------------------ | 
|  | // Generic FreeList methods (alloc/free related) | 
|  |  | 
|  | FreeList* FreeList::CreateFreeList() { return new FreeListManyCachedOrigin(); } | 
|  |  | 
|  | FreeSpace FreeList::TryFindNodeIn(FreeListCategoryType type, | 
|  | size_t minimum_size, size_t* node_size) { | 
|  | FreeListCategory* category = categories_[type]; | 
|  | if (category == nullptr) return FreeSpace(); | 
|  | FreeSpace node = category->PickNodeFromList(minimum_size, node_size); | 
|  | if (!node.is_null()) { | 
|  | DecreaseAvailableBytes(*node_size); | 
|  | DCHECK(IsVeryLong() || Available() == SumFreeLists()); | 
|  | } | 
|  | if (category->is_empty()) { | 
|  | RemoveCategory(category); | 
|  | } | 
|  | return node; | 
|  | } | 
|  |  | 
|  | FreeSpace FreeList::SearchForNodeInList(FreeListCategoryType type, | 
|  | size_t minimum_size, | 
|  | size_t* node_size) { | 
|  | FreeListCategoryIterator it(this, type); | 
|  | FreeSpace node; | 
|  | while (it.HasNext()) { | 
|  | FreeListCategory* current = it.Next(); | 
|  | node = current->SearchForNodeInList(minimum_size, node_size); | 
|  | if (!node.is_null()) { | 
|  | DecreaseAvailableBytes(*node_size); | 
|  | DCHECK(IsVeryLong() || Available() == SumFreeLists()); | 
|  | if (current->is_empty()) { | 
|  | RemoveCategory(current); | 
|  | } | 
|  | return node; | 
|  | } | 
|  | } | 
|  | return node; | 
|  | } | 
|  |  | 
|  | size_t FreeList::Free(Address start, size_t size_in_bytes, FreeMode mode) { | 
|  | Page* page = Page::FromAddress(start); | 
|  | page->DecreaseAllocatedBytes(size_in_bytes); | 
|  |  | 
|  | // Blocks have to be a minimum size to hold free list items. | 
|  | if (size_in_bytes < min_block_size_) { | 
|  | page->add_wasted_memory(size_in_bytes); | 
|  | wasted_bytes_ += size_in_bytes; | 
|  | return size_in_bytes; | 
|  | } | 
|  |  | 
|  | // Insert other blocks at the head of a free list of the appropriate | 
|  | // magnitude. | 
|  | FreeListCategoryType type = SelectFreeListCategoryType(size_in_bytes); | 
|  | page->free_list_category(type)->Free(start, size_in_bytes, mode, this); | 
|  | DCHECK_EQ(page->AvailableInFreeList(), | 
|  | page->AvailableInFreeListFromAllocatedBytes()); | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | // ------------------------------------------------ | 
|  | // FreeListMany implementation | 
|  |  | 
|  | constexpr unsigned int FreeListMany::categories_min[kNumberOfCategories]; | 
|  |  | 
|  | FreeListMany::FreeListMany() { | 
|  | // Initializing base (FreeList) fields | 
|  | number_of_categories_ = kNumberOfCategories; | 
|  | last_category_ = number_of_categories_ - 1; | 
|  | min_block_size_ = kMinBlockSize; | 
|  | categories_ = new FreeListCategory*[number_of_categories_](); | 
|  |  | 
|  | Reset(); | 
|  | } | 
|  |  | 
|  | FreeListMany::~FreeListMany() { delete[] categories_; } | 
|  |  | 
|  | size_t FreeListMany::GuaranteedAllocatable(size_t maximum_freed) { | 
|  | if (maximum_freed < categories_min[0]) { | 
|  | return 0; | 
|  | } | 
|  | for (int cat = kFirstCategory + 1; cat <= last_category_; cat++) { | 
|  | if (maximum_freed < categories_min[cat]) { | 
|  | return categories_min[cat - 1]; | 
|  | } | 
|  | } | 
|  | return maximum_freed; | 
|  | } | 
|  |  | 
|  | Page* FreeListMany::GetPageForSize(size_t size_in_bytes) { | 
|  | FreeListCategoryType minimum_category = | 
|  | SelectFreeListCategoryType(size_in_bytes); | 
|  | Page* page = nullptr; | 
|  | for (int cat = minimum_category + 1; !page && cat <= last_category_; cat++) { | 
|  | page = GetPageForCategoryType(cat); | 
|  | } | 
|  | if (!page) { | 
|  | // Might return a page in which |size_in_bytes| will not fit. | 
|  | page = GetPageForCategoryType(minimum_category); | 
|  | } | 
|  | return page; | 
|  | } | 
|  |  | 
|  | FreeSpace FreeListMany::Allocate(size_t size_in_bytes, size_t* node_size, | 
|  | AllocationOrigin origin) { | 
|  | DCHECK_GE(kMaxBlockSize, size_in_bytes); | 
|  | FreeSpace node; | 
|  | FreeListCategoryType type = SelectFreeListCategoryType(size_in_bytes); | 
|  | for (int i = type; i < last_category_ && node.is_null(); i++) { | 
|  | node = TryFindNodeIn(static_cast<FreeListCategoryType>(i), size_in_bytes, | 
|  | node_size); | 
|  | } | 
|  |  | 
|  | if (node.is_null()) { | 
|  | // Searching each element of the last category. | 
|  | node = SearchForNodeInList(last_category_, size_in_bytes, node_size); | 
|  | } | 
|  |  | 
|  | if (!node.is_null()) { | 
|  | Page::FromHeapObject(node)->IncreaseAllocatedBytes(*node_size); | 
|  | } | 
|  |  | 
|  | DCHECK(IsVeryLong() || Available() == SumFreeLists()); | 
|  | return node; | 
|  | } | 
|  |  | 
|  | // ------------------------------------------------ | 
|  | // FreeListManyCached implementation | 
|  |  | 
|  | FreeListManyCached::FreeListManyCached() { ResetCache(); } | 
|  |  | 
|  | void FreeListManyCached::Reset() { | 
|  | ResetCache(); | 
|  | FreeListMany::Reset(); | 
|  | } | 
|  |  | 
|  | bool FreeListManyCached::AddCategory(FreeListCategory* category) { | 
|  | bool was_added = FreeList::AddCategory(category); | 
|  |  | 
|  | // Updating cache | 
|  | if (was_added) { | 
|  | UpdateCacheAfterAddition(category->type_); | 
|  | } | 
|  |  | 
|  | #ifdef DEBUG | 
|  | CheckCacheIntegrity(); | 
|  | #endif | 
|  |  | 
|  | return was_added; | 
|  | } | 
|  |  | 
|  | void FreeListManyCached::RemoveCategory(FreeListCategory* category) { | 
|  | FreeList::RemoveCategory(category); | 
|  |  | 
|  | // Updating cache | 
|  | int type = category->type_; | 
|  | if (categories_[type] == nullptr) { | 
|  | UpdateCacheAfterRemoval(type); | 
|  | } | 
|  |  | 
|  | #ifdef DEBUG | 
|  | CheckCacheIntegrity(); | 
|  | #endif | 
|  | } | 
|  |  | 
|  | size_t FreeListManyCached::Free(Address start, size_t size_in_bytes, | 
|  | FreeMode mode) { | 
|  | Page* page = Page::FromAddress(start); | 
|  | page->DecreaseAllocatedBytes(size_in_bytes); | 
|  |  | 
|  | // Blocks have to be a minimum size to hold free list items. | 
|  | if (size_in_bytes < min_block_size_) { | 
|  | page->add_wasted_memory(size_in_bytes); | 
|  | wasted_bytes_ += size_in_bytes; | 
|  | return size_in_bytes; | 
|  | } | 
|  |  | 
|  | // Insert other blocks at the head of a free list of the appropriate | 
|  | // magnitude. | 
|  | FreeListCategoryType type = SelectFreeListCategoryType(size_in_bytes); | 
|  | page->free_list_category(type)->Free(start, size_in_bytes, mode, this); | 
|  |  | 
|  | // Updating cache | 
|  | if (mode == kLinkCategory) { | 
|  | UpdateCacheAfterAddition(type); | 
|  |  | 
|  | #ifdef DEBUG | 
|  | CheckCacheIntegrity(); | 
|  | #endif | 
|  | } | 
|  |  | 
|  | DCHECK_EQ(page->AvailableInFreeList(), | 
|  | page->AvailableInFreeListFromAllocatedBytes()); | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | FreeSpace FreeListManyCached::Allocate(size_t size_in_bytes, size_t* node_size, | 
|  | AllocationOrigin origin) { | 
|  | USE(origin); | 
|  | DCHECK_GE(kMaxBlockSize, size_in_bytes); | 
|  |  | 
|  | FreeSpace node; | 
|  | FreeListCategoryType type = SelectFreeListCategoryType(size_in_bytes); | 
|  | type = next_nonempty_category[type]; | 
|  | for (; type < last_category_; type = next_nonempty_category[type + 1]) { | 
|  | node = TryFindNodeIn(type, size_in_bytes, node_size); | 
|  | if (!node.is_null()) break; | 
|  | } | 
|  |  | 
|  | if (node.is_null()) { | 
|  | // Searching each element of the last category. | 
|  | type = last_category_; | 
|  | node = SearchForNodeInList(type, size_in_bytes, node_size); | 
|  | } | 
|  |  | 
|  | // Updating cache | 
|  | if (!node.is_null() && categories_[type] == nullptr) { | 
|  | UpdateCacheAfterRemoval(type); | 
|  | } | 
|  |  | 
|  | #ifdef DEBUG | 
|  | CheckCacheIntegrity(); | 
|  | #endif | 
|  |  | 
|  | if (!node.is_null()) { | 
|  | Page::FromHeapObject(node)->IncreaseAllocatedBytes(*node_size); | 
|  | } | 
|  |  | 
|  | DCHECK(IsVeryLong() || Available() == SumFreeLists()); | 
|  | return node; | 
|  | } | 
|  |  | 
|  | // ------------------------------------------------ | 
|  | // FreeListManyCachedFastPath implementation | 
|  |  | 
|  | FreeSpace FreeListManyCachedFastPath::Allocate(size_t size_in_bytes, | 
|  | size_t* node_size, | 
|  | AllocationOrigin origin) { | 
|  | USE(origin); | 
|  | DCHECK_GE(kMaxBlockSize, size_in_bytes); | 
|  | FreeSpace node; | 
|  |  | 
|  | // Fast path part 1: searching the last categories | 
|  | FreeListCategoryType first_category = | 
|  | SelectFastAllocationFreeListCategoryType(size_in_bytes); | 
|  | FreeListCategoryType type = first_category; | 
|  | for (type = next_nonempty_category[type]; type <= last_category_; | 
|  | type = next_nonempty_category[type + 1]) { | 
|  | node = TryFindNodeIn(type, size_in_bytes, node_size); | 
|  | if (!node.is_null()) break; | 
|  | } | 
|  |  | 
|  | // Fast path part 2: searching the medium categories for tiny objects | 
|  | if (node.is_null()) { | 
|  | if (size_in_bytes <= kTinyObjectMaxSize) { | 
|  | for (type = next_nonempty_category[kFastPathFallBackTiny]; | 
|  | type < kFastPathFirstCategory; | 
|  | type = next_nonempty_category[type + 1]) { | 
|  | node = TryFindNodeIn(type, size_in_bytes, node_size); | 
|  | if (!node.is_null()) break; | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | // Searching the last category | 
|  | if (node.is_null()) { | 
|  | // Searching each element of the last category. | 
|  | type = last_category_; | 
|  | node = SearchForNodeInList(type, size_in_bytes, node_size); | 
|  | } | 
|  |  | 
|  | // Finally, search the most precise category | 
|  | if (node.is_null()) { | 
|  | type = SelectFreeListCategoryType(size_in_bytes); | 
|  | for (type = next_nonempty_category[type]; type < first_category; | 
|  | type = next_nonempty_category[type + 1]) { | 
|  | node = TryFindNodeIn(type, size_in_bytes, node_size); | 
|  | if (!node.is_null()) break; | 
|  | } | 
|  | } | 
|  |  | 
|  | // Updating cache | 
|  | if (!node.is_null() && categories_[type] == nullptr) { | 
|  | UpdateCacheAfterRemoval(type); | 
|  | } | 
|  |  | 
|  | #ifdef DEBUG | 
|  | CheckCacheIntegrity(); | 
|  | #endif | 
|  |  | 
|  | if (!node.is_null()) { | 
|  | Page::FromHeapObject(node)->IncreaseAllocatedBytes(*node_size); | 
|  | } | 
|  |  | 
|  | DCHECK(IsVeryLong() || Available() == SumFreeLists()); | 
|  | return node; | 
|  | } | 
|  |  | 
|  | // ------------------------------------------------ | 
|  | // FreeListManyCachedOrigin implementation | 
|  |  | 
|  | FreeSpace FreeListManyCachedOrigin::Allocate(size_t size_in_bytes, | 
|  | size_t* node_size, | 
|  | AllocationOrigin origin) { | 
|  | if (origin == AllocationOrigin::kGC) { | 
|  | return FreeListManyCached::Allocate(size_in_bytes, node_size, origin); | 
|  | } else { | 
|  | return FreeListManyCachedFastPath::Allocate(size_in_bytes, node_size, | 
|  | origin); | 
|  | } | 
|  | } | 
|  |  | 
|  | // ------------------------------------------------ | 
|  | // Generic FreeList methods (non alloc/free related) | 
|  |  | 
|  | void FreeList::Reset() { | 
|  | ForAllFreeListCategories( | 
|  | [this](FreeListCategory* category) { category->Reset(this); }); | 
|  | for (int i = kFirstCategory; i < number_of_categories_; i++) { | 
|  | categories_[i] = nullptr; | 
|  | } | 
|  | wasted_bytes_ = 0; | 
|  | available_ = 0; | 
|  | } | 
|  |  | 
|  | size_t FreeList::EvictFreeListItems(Page* page) { | 
|  | size_t sum = 0; | 
|  | page->ForAllFreeListCategories([this, &sum](FreeListCategory* category) { | 
|  | sum += category->available(); | 
|  | RemoveCategory(category); | 
|  | category->Reset(this); | 
|  | }); | 
|  | return sum; | 
|  | } | 
|  |  | 
|  | void FreeList::RepairLists(Heap* heap) { | 
|  | ForAllFreeListCategories( | 
|  | [heap](FreeListCategory* category) { category->RepairFreeList(heap); }); | 
|  | } | 
|  |  | 
|  | bool FreeList::AddCategory(FreeListCategory* category) { | 
|  | FreeListCategoryType type = category->type_; | 
|  | DCHECK_LT(type, number_of_categories_); | 
|  | FreeListCategory* top = categories_[type]; | 
|  |  | 
|  | if (category->is_empty()) return false; | 
|  | DCHECK_NE(top, category); | 
|  |  | 
|  | // Common double-linked list insertion. | 
|  | if (top != nullptr) { | 
|  | top->set_prev(category); | 
|  | } | 
|  | category->set_next(top); | 
|  | categories_[type] = category; | 
|  |  | 
|  | IncreaseAvailableBytes(category->available()); | 
|  | return true; | 
|  | } | 
|  |  | 
|  | void FreeList::RemoveCategory(FreeListCategory* category) { | 
|  | FreeListCategoryType type = category->type_; | 
|  | DCHECK_LT(type, number_of_categories_); | 
|  | FreeListCategory* top = categories_[type]; | 
|  |  | 
|  | if (category->is_linked(this)) { | 
|  | DecreaseAvailableBytes(category->available()); | 
|  | } | 
|  |  | 
|  | // Common double-linked list removal. | 
|  | if (top == category) { | 
|  | categories_[type] = category->next(); | 
|  | } | 
|  | if (category->prev() != nullptr) { | 
|  | category->prev()->set_next(category->next()); | 
|  | } | 
|  | if (category->next() != nullptr) { | 
|  | category->next()->set_prev(category->prev()); | 
|  | } | 
|  | category->set_next(nullptr); | 
|  | category->set_prev(nullptr); | 
|  | } | 
|  |  | 
|  | void FreeList::PrintCategories(FreeListCategoryType type) { | 
|  | FreeListCategoryIterator it(this, type); | 
|  | PrintF("FreeList[%p, top=%p, %d] ", static_cast<void*>(this), | 
|  | static_cast<void*>(categories_[type]), type); | 
|  | while (it.HasNext()) { | 
|  | FreeListCategory* current = it.Next(); | 
|  | PrintF("%p -> ", static_cast<void*>(current)); | 
|  | } | 
|  | PrintF("null\n"); | 
|  | } | 
|  |  | 
|  | size_t FreeListCategory::SumFreeList() { | 
|  | size_t sum = 0; | 
|  | FreeSpace cur = top(); | 
|  | while (!cur.is_null()) { | 
|  | // We can't use "cur->map()" here because both cur's map and the | 
|  | // root can be null during bootstrapping. | 
|  | DCHECK(cur.map_slot().contains_value(Page::FromHeapObject(cur) | 
|  | ->heap() | 
|  | ->isolate() | 
|  | ->root(RootIndex::kFreeSpaceMap) | 
|  | .ptr())); | 
|  | sum += cur.relaxed_read_size(); | 
|  | cur = cur.next(); | 
|  | } | 
|  | return sum; | 
|  | } | 
|  | int FreeListCategory::FreeListLength() { | 
|  | int length = 0; | 
|  | FreeSpace cur = top(); | 
|  | while (!cur.is_null()) { | 
|  | length++; | 
|  | cur = cur.next(); | 
|  | } | 
|  | return length; | 
|  | } | 
|  |  | 
|  | #ifdef DEBUG | 
|  | bool FreeList::IsVeryLong() { | 
|  | int len = 0; | 
|  | for (int i = kFirstCategory; i < number_of_categories_; i++) { | 
|  | FreeListCategoryIterator it(this, static_cast<FreeListCategoryType>(i)); | 
|  | while (it.HasNext()) { | 
|  | len += it.Next()->FreeListLength(); | 
|  | if (len >= FreeListCategory::kVeryLongFreeList) return true; | 
|  | } | 
|  | } | 
|  | return false; | 
|  | } | 
|  |  | 
|  | // This can take a very long time because it is linear in the number of entries | 
|  | // on the free list, so it should not be called if FreeListLength returns | 
|  | // kVeryLongFreeList. | 
|  | size_t FreeList::SumFreeLists() { | 
|  | size_t sum = 0; | 
|  | ForAllFreeListCategories( | 
|  | [&sum](FreeListCategory* category) { sum += category->SumFreeList(); }); | 
|  | return sum; | 
|  | } | 
|  | #endif | 
|  |  | 
|  | }  // namespace internal | 
|  | }  // namespace v8 |