1 // Copyright 2020 the V8 project authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 #ifndef V8_HEAP_FREE_LIST_H_ 6 #define V8_HEAP_FREE_LIST_H_ 7 8 #include "src/base/macros.h" 9 #include "src/common/globals.h" 10 #include "src/heap/memory-chunk.h" 11 #include "src/objects/free-space.h" 12 #include "src/objects/map.h" 13 #include "src/utils/utils.h" 14 #include "testing/gtest/include/gtest/gtest_prod.h" // nogncheck 15 16 namespace v8 { 17 namespace internal { 18 19 namespace heap { 20 class HeapTester; 21 class TestCodePageAllocatorScope; 22 } // namespace heap 23 24 class AllocationObserver; 25 class FreeList; 26 class Isolate; 27 class LargeObjectSpace; 28 class LargePage; 29 class LinearAllocationArea; 30 class Page; 31 class PagedSpace; 32 class SemiSpace; 33 34 using FreeListCategoryType = int32_t; 35 36 static const FreeListCategoryType kFirstCategory = 0; 37 static const FreeListCategoryType kInvalidCategory = -1; 38 39 enum FreeMode { kLinkCategory, kDoNotLinkCategory }; 40 41 enum class SpaceAccountingMode { kSpaceAccounted, kSpaceUnaccounted }; 42 43 // A free list category maintains a linked list of free memory blocks. 44 class FreeListCategory { 45 public: Initialize(FreeListCategoryType type)46 void Initialize(FreeListCategoryType type) { 47 type_ = type; 48 available_ = 0; 49 prev_ = nullptr; 50 next_ = nullptr; 51 } 52 53 void Reset(FreeList* owner); 54 55 void RepairFreeList(Heap* heap); 56 57 // Relinks the category into the currently owning free list. Requires that the 58 // category is currently unlinked. 59 void Relink(FreeList* owner); 60 61 void Free(Address address, size_t size_in_bytes, FreeMode mode, 62 FreeList* owner); 63 64 // Performs a single try to pick a node of at least |minimum_size| from the 65 // category. Stores the actual size in |node_size|. Returns nullptr if no 66 // node is found. 67 FreeSpace PickNodeFromList(size_t minimum_size, size_t* node_size); 68 69 // Picks a node of at least |minimum_size| from the category. Stores the 70 // actual size in |node_size|. Returns nullptr if no node is found. 71 FreeSpace SearchForNodeInList(size_t minimum_size, size_t* node_size); 72 73 inline bool is_linked(FreeList* owner) const; is_empty()74 bool is_empty() { return top().is_null(); } available()75 uint32_t available() const { return available_; } 76 77 size_t SumFreeList(); 78 int FreeListLength(); 79 80 private: 81 // For debug builds we accurately compute free lists lengths up until 82 // {kVeryLongFreeList} by manually walking the list. 83 static const int kVeryLongFreeList = 500; 84 85 // Updates |available_|, |length_| and free_list_->Available() after an 86 // allocation of size |allocation_size|. 87 inline void UpdateCountersAfterAllocation(size_t allocation_size); 88 top()89 FreeSpace top() { return top_; } set_top(FreeSpace top)90 void set_top(FreeSpace top) { top_ = top; } prev()91 FreeListCategory* prev() { return prev_; } set_prev(FreeListCategory * prev)92 void set_prev(FreeListCategory* prev) { prev_ = prev; } next()93 FreeListCategory* next() { return next_; } set_next(FreeListCategory * next)94 void set_next(FreeListCategory* next) { next_ = next; } 95 96 // |type_|: The type of this free list category. 97 FreeListCategoryType type_ = kInvalidCategory; 98 99 // |available_|: Total available bytes in all blocks of this free list 100 // category. 101 uint32_t available_ = 0; 102 103 // |top_|: Points to the top FreeSpace in the free list category. 104 FreeSpace top_; 105 106 FreeListCategory* prev_ = nullptr; 107 FreeListCategory* next_ = nullptr; 108 109 friend class FreeList; 110 friend class FreeListManyCached; 111 friend class PagedSpace; 112 friend class MapSpace; 113 }; 114 115 // A free list maintains free blocks of memory. The free list is organized in 116 // a way to encourage objects allocated around the same time to be near each 117 // other. The normal way to allocate is intended to be by bumping a 'top' 118 // pointer until it hits a 'limit' pointer. When the limit is hit we need to 119 // find a new space to allocate from. This is done with the free list, which is 120 // divided up into rough categories to cut down on waste. Having finer 121 // categories would scatter allocation more. 122 class FreeList { 123 public: 124 // Creates a Freelist of the default class (FreeListLegacy for now). 125 V8_EXPORT_PRIVATE static FreeList* CreateFreeList(); 126 127 virtual ~FreeList() = default; 128 129 // Returns how much memory can be allocated after freeing maximum_freed 130 // memory. 131 virtual size_t GuaranteedAllocatable(size_t maximum_freed) = 0; 132 133 // Adds a node on the free list. The block of size {size_in_bytes} starting 134 // at {start} is placed on the free list. The return value is the number of 135 // bytes that were not added to the free list, because the freed memory block 136 // was too small. Bookkeeping information will be written to the block, i.e., 137 // its contents will be destroyed. The start address should be word aligned, 138 // and the size should be a non-zero multiple of the word size. 139 virtual size_t Free(Address start, size_t size_in_bytes, FreeMode mode); 140 141 // Allocates a free space node frome the free list of at least size_in_bytes 142 // bytes. Returns the actual node size in node_size which can be bigger than 143 // size_in_bytes. This method returns null if the allocation request cannot be 144 // handled by the free list. 145 virtual V8_WARN_UNUSED_RESULT FreeSpace Allocate(size_t size_in_bytes, 146 size_t* node_size, 147 AllocationOrigin origin) = 0; 148 149 // Returns a page containing an entry for a given type, or nullptr otherwise. 150 V8_EXPORT_PRIVATE virtual Page* GetPageForSize(size_t size_in_bytes) = 0; 151 152 virtual void Reset(); 153 154 // Return the number of bytes available on the free list. Available()155 size_t Available() { 156 DCHECK(available_ == SumFreeLists()); 157 return available_; 158 } 159 160 // Update number of available bytes on the Freelists. IncreaseAvailableBytes(size_t bytes)161 void IncreaseAvailableBytes(size_t bytes) { available_ += bytes; } DecreaseAvailableBytes(size_t bytes)162 void DecreaseAvailableBytes(size_t bytes) { available_ -= bytes; } 163 IsEmpty()164 bool IsEmpty() { 165 bool empty = true; 166 ForAllFreeListCategories([&empty](FreeListCategory* category) { 167 if (!category->is_empty()) empty = false; 168 }); 169 return empty; 170 } 171 172 // Used after booting the VM. 173 void RepairLists(Heap* heap); 174 175 V8_EXPORT_PRIVATE size_t EvictFreeListItems(Page* page); 176 number_of_categories()177 int number_of_categories() { return number_of_categories_; } last_category()178 FreeListCategoryType last_category() { return last_category_; } 179 wasted_bytes()180 size_t wasted_bytes() { return wasted_bytes_; } 181 182 template <typename Callback> ForAllFreeListCategories(FreeListCategoryType type,Callback callback)183 void ForAllFreeListCategories(FreeListCategoryType type, Callback callback) { 184 FreeListCategory* current = categories_[type]; 185 while (current != nullptr) { 186 FreeListCategory* next = current->next(); 187 callback(current); 188 current = next; 189 } 190 } 191 192 template <typename Callback> ForAllFreeListCategories(Callback callback)193 void ForAllFreeListCategories(Callback callback) { 194 for (int i = kFirstCategory; i < number_of_categories(); i++) { 195 ForAllFreeListCategories(static_cast<FreeListCategoryType>(i), callback); 196 } 197 } 198 199 virtual bool AddCategory(FreeListCategory* category); 200 virtual V8_EXPORT_PRIVATE void RemoveCategory(FreeListCategory* category); 201 void PrintCategories(FreeListCategoryType type); 202 203 protected: 204 class FreeListCategoryIterator final { 205 public: FreeListCategoryIterator(FreeList * free_list,FreeListCategoryType type)206 FreeListCategoryIterator(FreeList* free_list, FreeListCategoryType type) 207 : current_(free_list->categories_[type]) {} 208 HasNext()209 bool HasNext() const { return current_ != nullptr; } 210 Next()211 FreeListCategory* Next() { 212 DCHECK(HasNext()); 213 FreeListCategory* tmp = current_; 214 current_ = current_->next(); 215 return tmp; 216 } 217 218 private: 219 FreeListCategory* current_; 220 }; 221 222 #ifdef DEBUG 223 V8_EXPORT_PRIVATE size_t SumFreeLists(); 224 bool IsVeryLong(); 225 #endif 226 227 // Tries to retrieve a node from the first category in a given |type|. 228 // Returns nullptr if the category is empty or the top entry is smaller 229 // than minimum_size. 230 FreeSpace TryFindNodeIn(FreeListCategoryType type, size_t minimum_size, 231 size_t* node_size); 232 233 // Searches a given |type| for a node of at least |minimum_size|. 234 FreeSpace SearchForNodeInList(FreeListCategoryType type, size_t minimum_size, 235 size_t* node_size); 236 237 // Returns the smallest category in which an object of |size_in_bytes| could 238 // fit. 239 virtual FreeListCategoryType SelectFreeListCategoryType( 240 size_t size_in_bytes) = 0; 241 top(FreeListCategoryType type)242 FreeListCategory* top(FreeListCategoryType type) const { 243 return categories_[type]; 244 } 245 246 inline Page* GetPageForCategoryType(FreeListCategoryType type); 247 248 int number_of_categories_ = 0; 249 FreeListCategoryType last_category_ = 0; 250 size_t min_block_size_ = 0; 251 252 std::atomic<size_t> wasted_bytes_{0}; 253 FreeListCategory** categories_ = nullptr; 254 255 // |available_|: The number of bytes in this freelist. 256 size_t available_ = 0; 257 258 friend class FreeListCategory; 259 friend class Page; 260 friend class MemoryChunk; 261 friend class ReadOnlyPage; 262 friend class MapSpace; 263 }; 264 265 // FreeList used for spaces that don't have freelists 266 // (only the LargeObject space for now). 267 class NoFreeList final : public FreeList { 268 public: GuaranteedAllocatable(size_t maximum_freed)269 size_t GuaranteedAllocatable(size_t maximum_freed) final { 270 FATAL("NoFreeList can't be used as a standard FreeList. "); 271 } Free(Address start,size_t size_in_bytes,FreeMode mode)272 size_t Free(Address start, size_t size_in_bytes, FreeMode mode) final { 273 FATAL("NoFreeList can't be used as a standard FreeList."); 274 } Allocate(size_t size_in_bytes,size_t * node_size,AllocationOrigin origin)275 V8_WARN_UNUSED_RESULT FreeSpace Allocate(size_t size_in_bytes, 276 size_t* node_size, 277 AllocationOrigin origin) final { 278 FATAL("NoFreeList can't be used as a standard FreeList."); 279 } GetPageForSize(size_t size_in_bytes)280 Page* GetPageForSize(size_t size_in_bytes) final { 281 FATAL("NoFreeList can't be used as a standard FreeList."); 282 } 283 284 private: SelectFreeListCategoryType(size_t size_in_bytes)285 FreeListCategoryType SelectFreeListCategoryType(size_t size_in_bytes) final { 286 FATAL("NoFreeList can't be used as a standard FreeList."); 287 } 288 }; 289 290 // Use 24 Freelists: on per 16 bytes between 24 and 256, and then a few ones for 291 // larger sizes. See the variable |categories_min| for the size of each 292 // Freelist. Allocation is done using a best-fit strategy (considering only the 293 // first element of each category though). 294 // Performances are expected to be worst than FreeListLegacy, but memory 295 // consumption should be lower (since fragmentation should be lower). 296 class V8_EXPORT_PRIVATE FreeListMany : public FreeList { 297 public: 298 size_t GuaranteedAllocatable(size_t maximum_freed) override; 299 300 Page* GetPageForSize(size_t size_in_bytes) override; 301 302 FreeListMany(); 303 ~FreeListMany() override; 304 305 V8_WARN_UNUSED_RESULT FreeSpace Allocate(size_t size_in_bytes, 306 size_t* node_size, 307 AllocationOrigin origin) override; 308 309 protected: 310 static const size_t kMinBlockSize = 3 * kTaggedSize; 311 312 // This is a conservative upper bound. The actual maximum block size takes 313 // padding and alignment of data and code pages into account. 314 static const size_t kMaxBlockSize = MemoryChunk::kPageSize; 315 // Largest size for which categories are still precise, and for which we can 316 // therefore compute the category in constant time. 317 static const size_t kPreciseCategoryMaxSize = 256; 318 319 // Categories boundaries generated with: 320 // perl -E ' 321 // @cat = (24, map {$_*16} 2..16, 48, 64); 322 // while ($cat[-1] <= 32768) { 323 // push @cat, $cat[-1]*2 324 // } 325 // say join ", ", @cat; 326 // say "\n", scalar @cat' 327 static const int kNumberOfCategories = 24; 328 static constexpr unsigned int categories_min[kNumberOfCategories] = { 329 24, 32, 48, 64, 80, 96, 112, 128, 144, 160, 176, 192, 330 208, 224, 240, 256, 512, 1024, 2048, 4096, 8192, 16384, 32768, 65536}; 331 332 // Return the smallest category that could hold |size_in_bytes| bytes. SelectFreeListCategoryType(size_t size_in_bytes)333 FreeListCategoryType SelectFreeListCategoryType( 334 size_t size_in_bytes) override { 335 if (size_in_bytes <= kPreciseCategoryMaxSize) { 336 if (size_in_bytes < categories_min[1]) return 0; 337 return static_cast<FreeListCategoryType>(size_in_bytes >> 4) - 1; 338 } 339 for (int cat = (kPreciseCategoryMaxSize >> 4) - 1; cat < last_category_; 340 cat++) { 341 if (size_in_bytes < categories_min[cat + 1]) { 342 return cat; 343 } 344 } 345 return last_category_; 346 } 347 348 FRIEND_TEST(SpacesTest, FreeListManySelectFreeListCategoryType); 349 FRIEND_TEST(SpacesTest, FreeListManyGuaranteedAllocatable); 350 }; 351 352 // Same as FreeListMany but uses a cache to know which categories are empty. 353 // The cache (|next_nonempty_category|) is maintained in a way such that for 354 // each category c, next_nonempty_category[c] contains the first non-empty 355 // category greater or equal to c, that may hold an object of size c. 356 // Allocation is done using the same strategy as FreeListMany (ie, best fit). 357 class V8_EXPORT_PRIVATE FreeListManyCached : public FreeListMany { 358 public: 359 FreeListManyCached(); 360 361 V8_WARN_UNUSED_RESULT FreeSpace Allocate(size_t size_in_bytes, 362 size_t* node_size, 363 AllocationOrigin origin) override; 364 365 size_t Free(Address start, size_t size_in_bytes, FreeMode mode) override; 366 367 void Reset() override; 368 369 bool AddCategory(FreeListCategory* category) override; 370 void RemoveCategory(FreeListCategory* category) override; 371 372 protected: 373 // Updates the cache after adding something in the category |cat|. UpdateCacheAfterAddition(FreeListCategoryType cat)374 void UpdateCacheAfterAddition(FreeListCategoryType cat) { 375 for (int i = cat; i >= kFirstCategory && next_nonempty_category[i] > cat; 376 i--) { 377 next_nonempty_category[i] = cat; 378 } 379 } 380 381 // Updates the cache after emptying category |cat|. UpdateCacheAfterRemoval(FreeListCategoryType cat)382 void UpdateCacheAfterRemoval(FreeListCategoryType cat) { 383 for (int i = cat; i >= kFirstCategory && next_nonempty_category[i] == cat; 384 i--) { 385 next_nonempty_category[i] = next_nonempty_category[cat + 1]; 386 } 387 } 388 389 #ifdef DEBUG CheckCacheIntegrity()390 void CheckCacheIntegrity() { 391 for (int i = 0; i <= last_category_; i++) { 392 DCHECK(next_nonempty_category[i] == last_category_ + 1 || 393 categories_[next_nonempty_category[i]] != nullptr); 394 for (int j = i; j < next_nonempty_category[i]; j++) { 395 DCHECK(categories_[j] == nullptr); 396 } 397 } 398 } 399 #endif 400 401 // The cache is overallocated by one so that the last element is always 402 // defined, and when updating the cache, we can always use cache[i+1] as long 403 // as i is < kNumberOfCategories. 404 int next_nonempty_category[kNumberOfCategories + 1]; 405 406 private: ResetCache()407 void ResetCache() { 408 for (int i = 0; i < kNumberOfCategories; i++) { 409 next_nonempty_category[i] = kNumberOfCategories; 410 } 411 // Setting the after-last element as well, as explained in the cache's 412 // declaration. 413 next_nonempty_category[kNumberOfCategories] = kNumberOfCategories; 414 } 415 }; 416 417 // Same as FreeListManyCached but uses a fast path. 418 // The fast path overallocates by at least 1.85k bytes. The idea of this 1.85k 419 // is: we want the fast path to always overallocate, even for larger 420 // categories. Therefore, we have two choices: either overallocate by 421 // "size_in_bytes * something" or overallocate by "size_in_bytes + 422 // something". We choose the later, as the former will tend to overallocate too 423 // much for larger objects. The 1.85k (= 2048 - 128) has been chosen such that 424 // for tiny objects (size <= 128 bytes), the first category considered is the 425 // 36th (which holds objects of 2k to 3k), while for larger objects, the first 426 // category considered will be one that guarantees a 1.85k+ bytes 427 // overallocation. Using 2k rather than 1.85k would have resulted in either a 428 // more complex logic for SelectFastAllocationFreeListCategoryType, or the 36th 429 // category (2k to 3k) not being used; both of which are undesirable. 430 // A secondary fast path is used for tiny objects (size <= 128), in order to 431 // consider categories from 256 to 2048 bytes for them. 432 // Note that this class uses a precise GetPageForSize (inherited from 433 // FreeListMany), which makes its fast path less fast in the Scavenger. This is 434 // done on purpose, since this class's only purpose is to be used by 435 // FreeListManyCachedOrigin, which is precise for the scavenger. 436 class V8_EXPORT_PRIVATE FreeListManyCachedFastPath : public FreeListManyCached { 437 public: 438 V8_WARN_UNUSED_RESULT FreeSpace Allocate(size_t size_in_bytes, 439 size_t* node_size, 440 AllocationOrigin origin) override; 441 442 protected: 443 // Objects in the 18th category are at least 2048 bytes 444 static const FreeListCategoryType kFastPathFirstCategory = 18; 445 static const size_t kFastPathStart = 2048; 446 static const size_t kTinyObjectMaxSize = 128; 447 static const size_t kFastPathOffset = kFastPathStart - kTinyObjectMaxSize; 448 // Objects in the 15th category are at least 256 bytes 449 static const FreeListCategoryType kFastPathFallBackTiny = 15; 450 451 STATIC_ASSERT(categories_min[kFastPathFirstCategory] == kFastPathStart); 452 STATIC_ASSERT(categories_min[kFastPathFallBackTiny] == 453 kTinyObjectMaxSize * 2); 454 SelectFastAllocationFreeListCategoryType(size_t size_in_bytes)455 FreeListCategoryType SelectFastAllocationFreeListCategoryType( 456 size_t size_in_bytes) { 457 DCHECK(size_in_bytes < kMaxBlockSize); 458 459 if (size_in_bytes >= categories_min[last_category_]) return last_category_; 460 461 size_in_bytes += kFastPathOffset; 462 for (int cat = kFastPathFirstCategory; cat < last_category_; cat++) { 463 if (size_in_bytes <= categories_min[cat]) { 464 return cat; 465 } 466 } 467 return last_category_; 468 } 469 470 FRIEND_TEST( 471 SpacesTest, 472 FreeListManyCachedFastPathSelectFastAllocationFreeListCategoryType); 473 }; 474 475 // Uses FreeListManyCached if in the GC; FreeListManyCachedFastPath otherwise. 476 // The reasonning behind this FreeList is the following: the GC runs in 477 // parallel, and therefore, more expensive allocations there are less 478 // noticeable. On the other hand, the generated code and runtime need to be very 479 // fast. Therefore, the strategy for the former is one that is not very 480 // efficient, but reduces fragmentation (FreeListManyCached), while the strategy 481 // for the later is one that is very efficient, but introduces some 482 // fragmentation (FreeListManyCachedFastPath). 483 class V8_EXPORT_PRIVATE FreeListManyCachedOrigin 484 : public FreeListManyCachedFastPath { 485 public: 486 V8_WARN_UNUSED_RESULT FreeSpace Allocate(size_t size_in_bytes, 487 size_t* node_size, 488 AllocationOrigin origin) override; 489 }; 490 491 } // namespace internal 492 } // namespace v8 493 494 #endif // V8_HEAP_FREE_LIST_H_ 495