blob: e9bf77d1711e31e20b2aba2e5823165c2305f656 [file] [log] [blame]
// 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);
}
}
// ------------------------------------------------
// FreeListMap implementation
FreeListMap::FreeListMap() {
// Initializing base (FreeList) fields
number_of_categories_ = 1;
last_category_ = kOnlyCategory;
min_block_size_ = kMinBlockSize;
categories_ = new FreeListCategory*[number_of_categories_]();
Reset();
}
size_t FreeListMap::GuaranteedAllocatable(size_t maximum_freed) {
return maximum_freed;
}
Page* FreeListMap::GetPageForSize(size_t size_in_bytes) {
return GetPageForCategoryType(kOnlyCategory);
}
FreeListMap::~FreeListMap() { delete[] categories_; }
FreeSpace FreeListMap::Allocate(size_t size_in_bytes, size_t* node_size,
AllocationOrigin origin) {
DCHECK_GE(kMaxBlockSize, size_in_bytes);
// The following DCHECK ensures that maps are allocated one by one (ie,
// without folding). This assumption currently holds. However, if it were to
// become untrue in the future, you'll get an error here. To fix it, I would
// suggest removing the DCHECK, and replacing TryFindNodeIn by
// SearchForNodeInList below.
DCHECK_EQ(size_in_bytes, Map::kSize);
FreeSpace node = TryFindNodeIn(kOnlyCategory, size_in_bytes, node_size);
if (!node.is_null()) {
Page::FromHeapObject(node)->IncreaseAllocatedBytes(*node_size);
}
DCHECK_IMPLIES(node.is_null(), IsEmpty());
return node;
}
// ------------------------------------------------
// 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