1 //===-- primary64.h ---------------------------------------------*- C++ -*-===// 2 // 3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. 4 // See https://llvm.org/LICENSE.txt for license information. 5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception 6 // 7 //===----------------------------------------------------------------------===// 8 9 #ifndef SCUDO_PRIMARY64_H_ 10 #define SCUDO_PRIMARY64_H_ 11 12 #include "allocator_common.h" 13 #include "bytemap.h" 14 #include "common.h" 15 #include "list.h" 16 #include "local_cache.h" 17 #include "mem_map.h" 18 #include "memtag.h" 19 #include "options.h" 20 #include "release.h" 21 #include "stats.h" 22 #include "string_utils.h" 23 #include "thread_annotations.h" 24 25 #include "condition_variable.h" 26 27 namespace scudo { 28 29 // SizeClassAllocator64 is an allocator tuned for 64-bit address space. 30 // 31 // It starts by reserving NumClasses * 2^RegionSizeLog bytes, equally divided in 32 // Regions, specific to each size class. Note that the base of that mapping is 33 // random (based to the platform specific map() capabilities). If 34 // PrimaryEnableRandomOffset is set, each Region actually starts at a random 35 // offset from its base. 36 // 37 // Regions are mapped incrementally on demand to fulfill allocation requests, 38 // those mappings being split into equally sized Blocks based on the size class 39 // they belong to. The Blocks created are shuffled to prevent predictable 40 // address patterns (the predictability increases with the size of the Blocks). 41 // 42 // The 1st Region (for size class 0) holds the TransferBatches. This is a 43 // structure used to transfer arrays of available pointers from the class size 44 // freelist to the thread specific freelist, and back. 45 // 46 // The memory used by this allocator is never unmapped, but can be partially 47 // released if the platform allows for it. 48 49 template <typename Config> class SizeClassAllocator64 { 50 public: 51 typedef typename Config::Primary::CompactPtrT CompactPtrT; 52 typedef typename Config::Primary::SizeClassMap SizeClassMap; 53 typedef typename ConditionVariableState< 54 typename Config::Primary>::ConditionVariableT ConditionVariableT; 55 static const uptr CompactPtrScale = Config::Primary::CompactPtrScale; 56 static const uptr RegionSizeLog = Config::Primary::RegionSizeLog; 57 static const uptr GroupSizeLog = Config::Primary::GroupSizeLog; 58 static_assert(RegionSizeLog >= GroupSizeLog, 59 "Group size shouldn't be greater than the region size"); 60 static const uptr GroupScale = GroupSizeLog - CompactPtrScale; 61 typedef SizeClassAllocator64<Config> ThisT; 62 typedef SizeClassAllocatorLocalCache<ThisT> CacheT; 63 typedef TransferBatch<ThisT> TransferBatchT; 64 typedef BatchGroup<ThisT> BatchGroupT; 65 66 static_assert(sizeof(BatchGroupT) <= sizeof(TransferBatchT), 67 "BatchGroupT uses the same class size as TransferBatchT"); 68 69 static uptr getSizeByClassId(uptr ClassId) { 70 return (ClassId == SizeClassMap::BatchClassId) 71 ? roundUp(sizeof(TransferBatchT), 1U << CompactPtrScale) 72 : SizeClassMap::getSizeByClassId(ClassId); 73 } 74 75 static bool canAllocate(uptr Size) { return Size <= SizeClassMap::MaxSize; } 76 77 static bool conditionVariableEnabled() { 78 return ConditionVariableState<typename Config::Primary>::enabled(); 79 } 80 81 void init(s32 ReleaseToOsInterval) NO_THREAD_SAFETY_ANALYSIS { 82 DCHECK(isAligned(reinterpret_cast<uptr>(this), alignof(ThisT))); 83 84 const uptr PageSize = getPageSizeCached(); 85 const uptr GroupSize = (1UL << GroupSizeLog); 86 const uptr PagesInGroup = GroupSize / PageSize; 87 const uptr MinSizeClass = getSizeByClassId(1); 88 // When trying to release pages back to memory, visiting smaller size 89 // classes is expensive. Therefore, we only try to release smaller size 90 // classes when the amount of free blocks goes over a certain threshold (See 91 // the comment in releaseToOSMaybe() for more details). For example, for 92 // size class 32, we only do the release when the size of free blocks is 93 // greater than 97% of pages in a group. However, this may introduce another 94 // issue that if the number of free blocks is bouncing between 97% ~ 100%. 95 // Which means we may try many page releases but only release very few of 96 // them (less than 3% in a group). Even though we have 97 // `&ReleaseToOsIntervalMs` which slightly reduce the frequency of these 98 // calls but it will be better to have another guard to mitigate this issue. 99 // 100 // Here we add another constraint on the minimum size requirement. The 101 // constraint is determined by the size of in-use blocks in the minimal size 102 // class. Take size class 32 as an example, 103 // 104 // +- one memory group -+ 105 // +----------------------+------+ 106 // | 97% of free blocks | | 107 // +----------------------+------+ 108 // \ / 109 // 3% in-use blocks 110 // 111 // * The release size threshold is 97%. 112 // 113 // The 3% size in a group is about 7 pages. For two consecutive 114 // releaseToOSMaybe(), we require the difference between `PushedBlocks` 115 // should be greater than 7 pages. This mitigates the page releasing 116 // thrashing which is caused by memory usage bouncing around the threshold. 117 // The smallest size class takes longest time to do the page release so we 118 // use its size of in-use blocks as a heuristic. 119 SmallerBlockReleasePageDelta = 120 PagesInGroup * (1 + MinSizeClass / 16U) / 100; 121 122 // Reserve the space required for the Primary. 123 CHECK(ReservedMemory.create(/*Addr=*/0U, PrimarySize, 124 "scudo:primary_reserve")); 125 PrimaryBase = ReservedMemory.getBase(); 126 DCHECK_NE(PrimaryBase, 0U); 127 128 u32 Seed; 129 const u64 Time = getMonotonicTimeFast(); 130 if (!getRandom(reinterpret_cast<void *>(&Seed), sizeof(Seed))) 131 Seed = static_cast<u32>(Time ^ (PrimaryBase >> 12)); 132 133 for (uptr I = 0; I < NumClasses; I++) { 134 RegionInfo *Region = getRegionInfo(I); 135 136 // The actual start of a region is offset by a random number of pages 137 // when PrimaryEnableRandomOffset is set. 138 Region->RegionBeg = (PrimaryBase + (I << RegionSizeLog)) + 139 (Config::Primary::EnableRandomOffset 140 ? ((getRandomModN(&Seed, 16) + 1) * PageSize) 141 : 0); 142 Region->RandState = getRandomU32(&Seed); 143 // Releasing small blocks is expensive, set a higher threshold to avoid 144 // frequent page releases. 145 if (isSmallBlock(getSizeByClassId(I))) 146 Region->TryReleaseThreshold = PageSize * SmallerBlockReleasePageDelta; 147 else 148 Region->TryReleaseThreshold = PageSize; 149 Region->ReleaseInfo.LastReleaseAtNs = Time; 150 151 Region->MemMapInfo.MemMap = ReservedMemory.dispatch( 152 PrimaryBase + (I << RegionSizeLog), RegionSize); 153 CHECK(Region->MemMapInfo.MemMap.isAllocated()); 154 } 155 shuffle(RegionInfoArray, NumClasses, &Seed); 156 157 // The binding should be done after region shuffling so that it won't bind 158 // the FLLock from the wrong region. 159 for (uptr I = 0; I < NumClasses; I++) 160 getRegionInfo(I)->FLLockCV.bindTestOnly(getRegionInfo(I)->FLLock); 161 162 setOption(Option::ReleaseInterval, static_cast<sptr>(ReleaseToOsInterval)); 163 } 164 165 void unmapTestOnly() NO_THREAD_SAFETY_ANALYSIS { 166 for (uptr I = 0; I < NumClasses; I++) { 167 RegionInfo *Region = getRegionInfo(I); 168 *Region = {}; 169 } 170 if (PrimaryBase) 171 ReservedMemory.release(); 172 PrimaryBase = 0U; 173 } 174 175 // When all blocks are freed, it has to be the same size as `AllocatedUser`. 176 void verifyAllBlocksAreReleasedTestOnly() { 177 // `BatchGroup` and `TransferBatch` also use the blocks from BatchClass. 178 uptr BatchClassUsedInFreeLists = 0; 179 for (uptr I = 0; I < NumClasses; I++) { 180 // We have to count BatchClassUsedInFreeLists in other regions first. 181 if (I == SizeClassMap::BatchClassId) 182 continue; 183 RegionInfo *Region = getRegionInfo(I); 184 ScopedLock ML(Region->MMLock); 185 ScopedLock FL(Region->FLLock); 186 const uptr BlockSize = getSizeByClassId(I); 187 uptr TotalBlocks = 0; 188 for (BatchGroupT &BG : Region->FreeListInfo.BlockList) { 189 // `BG::Batches` are `TransferBatches`. +1 for `BatchGroup`. 190 BatchClassUsedInFreeLists += BG.Batches.size() + 1; 191 for (const auto &It : BG.Batches) 192 TotalBlocks += It.getCount(); 193 } 194 195 DCHECK_EQ(TotalBlocks, Region->MemMapInfo.AllocatedUser / BlockSize); 196 DCHECK_EQ(Region->FreeListInfo.PushedBlocks, 197 Region->FreeListInfo.PoppedBlocks); 198 } 199 200 RegionInfo *Region = getRegionInfo(SizeClassMap::BatchClassId); 201 ScopedLock ML(Region->MMLock); 202 ScopedLock FL(Region->FLLock); 203 const uptr BlockSize = getSizeByClassId(SizeClassMap::BatchClassId); 204 uptr TotalBlocks = 0; 205 for (BatchGroupT &BG : Region->FreeListInfo.BlockList) { 206 if (LIKELY(!BG.Batches.empty())) { 207 for (const auto &It : BG.Batches) 208 TotalBlocks += It.getCount(); 209 } else { 210 // `BatchGroup` with empty freelist doesn't have `TransferBatch` record 211 // itself. 212 ++TotalBlocks; 213 } 214 } 215 DCHECK_EQ(TotalBlocks + BatchClassUsedInFreeLists, 216 Region->MemMapInfo.AllocatedUser / BlockSize); 217 DCHECK_GE(Region->FreeListInfo.PoppedBlocks, 218 Region->FreeListInfo.PushedBlocks); 219 const uptr BlocksInUse = 220 Region->FreeListInfo.PoppedBlocks - Region->FreeListInfo.PushedBlocks; 221 DCHECK_EQ(BlocksInUse, BatchClassUsedInFreeLists); 222 } 223 224 // Note that the `MaxBlockCount` will be used when we support arbitrary blocks 225 // count. Now it's the same as the number of blocks stored in the 226 // `TransferBatch`. 227 u16 popBlocks(CacheT *C, uptr ClassId, CompactPtrT *ToArray, 228 UNUSED const u16 MaxBlockCount) { 229 TransferBatchT *B = popBatch(C, ClassId); 230 if (!B) 231 return 0; 232 233 const u16 Count = B->getCount(); 234 DCHECK_GT(Count, 0U); 235 B->moveToArray(ToArray); 236 237 if (ClassId != SizeClassMap::BatchClassId) 238 C->deallocate(SizeClassMap::BatchClassId, B); 239 240 return Count; 241 } 242 243 TransferBatchT *popBatch(CacheT *C, uptr ClassId) { 244 DCHECK_LT(ClassId, NumClasses); 245 RegionInfo *Region = getRegionInfo(ClassId); 246 247 { 248 ScopedLock L(Region->FLLock); 249 TransferBatchT *B = popBatchImpl(C, ClassId, Region); 250 if (LIKELY(B)) 251 return B; 252 } 253 254 bool ReportRegionExhausted = false; 255 TransferBatchT *B = nullptr; 256 257 if (conditionVariableEnabled()) { 258 B = popBatchWithCV(C, ClassId, Region, ReportRegionExhausted); 259 } else { 260 while (true) { 261 // When two threads compete for `Region->MMLock`, we only want one of 262 // them to call populateFreeListAndPopBatch(). To avoid both of them 263 // doing that, always check the freelist before mapping new pages. 264 ScopedLock ML(Region->MMLock); 265 { 266 ScopedLock FL(Region->FLLock); 267 if ((B = popBatchImpl(C, ClassId, Region))) 268 break; 269 } 270 271 const bool RegionIsExhausted = Region->Exhausted; 272 if (!RegionIsExhausted) 273 B = populateFreeListAndPopBatch(C, ClassId, Region); 274 ReportRegionExhausted = !RegionIsExhausted && Region->Exhausted; 275 break; 276 } 277 } 278 279 if (UNLIKELY(ReportRegionExhausted)) { 280 Printf("Can't populate more pages for size class %zu.\n", 281 getSizeByClassId(ClassId)); 282 283 // Theoretically, BatchClass shouldn't be used up. Abort immediately when 284 // it happens. 285 if (ClassId == SizeClassMap::BatchClassId) 286 reportOutOfBatchClass(); 287 } 288 289 return B; 290 } 291 292 // Push the array of free blocks to the designated batch group. 293 void pushBlocks(CacheT *C, uptr ClassId, CompactPtrT *Array, u32 Size) { 294 DCHECK_LT(ClassId, NumClasses); 295 DCHECK_GT(Size, 0); 296 297 RegionInfo *Region = getRegionInfo(ClassId); 298 if (ClassId == SizeClassMap::BatchClassId) { 299 ScopedLock L(Region->FLLock); 300 pushBatchClassBlocks(Region, Array, Size); 301 if (conditionVariableEnabled()) 302 Region->FLLockCV.notifyAll(Region->FLLock); 303 return; 304 } 305 306 // TODO(chiahungduan): Consider not doing grouping if the group size is not 307 // greater than the block size with a certain scale. 308 309 bool SameGroup = true; 310 if (GroupSizeLog < RegionSizeLog) { 311 // Sort the blocks so that blocks belonging to the same group can be 312 // pushed together. 313 for (u32 I = 1; I < Size; ++I) { 314 if (compactPtrGroup(Array[I - 1]) != compactPtrGroup(Array[I])) 315 SameGroup = false; 316 CompactPtrT Cur = Array[I]; 317 u32 J = I; 318 while (J > 0 && compactPtrGroup(Cur) < compactPtrGroup(Array[J - 1])) { 319 Array[J] = Array[J - 1]; 320 --J; 321 } 322 Array[J] = Cur; 323 } 324 } 325 326 { 327 ScopedLock L(Region->FLLock); 328 pushBlocksImpl(C, ClassId, Region, Array, Size, SameGroup); 329 if (conditionVariableEnabled()) 330 Region->FLLockCV.notifyAll(Region->FLLock); 331 } 332 } 333 334 void disable() NO_THREAD_SAFETY_ANALYSIS { 335 // The BatchClassId must be locked last since other classes can use it. 336 for (sptr I = static_cast<sptr>(NumClasses) - 1; I >= 0; I--) { 337 if (static_cast<uptr>(I) == SizeClassMap::BatchClassId) 338 continue; 339 getRegionInfo(static_cast<uptr>(I))->MMLock.lock(); 340 getRegionInfo(static_cast<uptr>(I))->FLLock.lock(); 341 } 342 getRegionInfo(SizeClassMap::BatchClassId)->MMLock.lock(); 343 getRegionInfo(SizeClassMap::BatchClassId)->FLLock.lock(); 344 } 345 346 void enable() NO_THREAD_SAFETY_ANALYSIS { 347 getRegionInfo(SizeClassMap::BatchClassId)->FLLock.unlock(); 348 getRegionInfo(SizeClassMap::BatchClassId)->MMLock.unlock(); 349 for (uptr I = 0; I < NumClasses; I++) { 350 if (I == SizeClassMap::BatchClassId) 351 continue; 352 getRegionInfo(I)->FLLock.unlock(); 353 getRegionInfo(I)->MMLock.unlock(); 354 } 355 } 356 357 template <typename F> void iterateOverBlocks(F Callback) { 358 for (uptr I = 0; I < NumClasses; I++) { 359 if (I == SizeClassMap::BatchClassId) 360 continue; 361 RegionInfo *Region = getRegionInfo(I); 362 // TODO: The call of `iterateOverBlocks` requires disabling 363 // SizeClassAllocator64. We may consider locking each region on demand 364 // only. 365 Region->FLLock.assertHeld(); 366 Region->MMLock.assertHeld(); 367 const uptr BlockSize = getSizeByClassId(I); 368 const uptr From = Region->RegionBeg; 369 const uptr To = From + Region->MemMapInfo.AllocatedUser; 370 for (uptr Block = From; Block < To; Block += BlockSize) 371 Callback(Block); 372 } 373 } 374 375 void getStats(ScopedString *Str) { 376 // TODO(kostyak): get the RSS per region. 377 uptr TotalMapped = 0; 378 uptr PoppedBlocks = 0; 379 uptr PushedBlocks = 0; 380 for (uptr I = 0; I < NumClasses; I++) { 381 RegionInfo *Region = getRegionInfo(I); 382 { 383 ScopedLock L(Region->MMLock); 384 TotalMapped += Region->MemMapInfo.MappedUser; 385 } 386 { 387 ScopedLock L(Region->FLLock); 388 PoppedBlocks += Region->FreeListInfo.PoppedBlocks; 389 PushedBlocks += Region->FreeListInfo.PushedBlocks; 390 } 391 } 392 Str->append("Stats: SizeClassAllocator64: %zuM mapped (%uM rss) in %zu " 393 "allocations; remains %zu\n", 394 TotalMapped >> 20, 0U, PoppedBlocks, 395 PoppedBlocks - PushedBlocks); 396 397 for (uptr I = 0; I < NumClasses; I++) { 398 RegionInfo *Region = getRegionInfo(I); 399 ScopedLock L1(Region->MMLock); 400 ScopedLock L2(Region->FLLock); 401 getStats(Str, I, Region); 402 } 403 } 404 405 void getFragmentationInfo(ScopedString *Str) { 406 Str->append( 407 "Fragmentation Stats: SizeClassAllocator64: page size = %zu bytes\n", 408 getPageSizeCached()); 409 410 for (uptr I = 1; I < NumClasses; I++) { 411 RegionInfo *Region = getRegionInfo(I); 412 ScopedLock L(Region->MMLock); 413 getRegionFragmentationInfo(Region, I, Str); 414 } 415 } 416 417 bool setOption(Option O, sptr Value) { 418 if (O == Option::ReleaseInterval) { 419 const s32 Interval = Max(Min(static_cast<s32>(Value), 420 Config::Primary::MaxReleaseToOsIntervalMs), 421 Config::Primary::MinReleaseToOsIntervalMs); 422 atomic_store_relaxed(&ReleaseToOsIntervalMs, Interval); 423 return true; 424 } 425 // Not supported by the Primary, but not an error either. 426 return true; 427 } 428 429 uptr tryReleaseToOS(uptr ClassId, ReleaseToOS ReleaseType) { 430 RegionInfo *Region = getRegionInfo(ClassId); 431 // Note that the tryLock() may fail spuriously, given that it should rarely 432 // happen and page releasing is fine to skip, we don't take certain 433 // approaches to ensure one page release is done. 434 if (Region->MMLock.tryLock()) { 435 uptr BytesReleased = releaseToOSMaybe(Region, ClassId, ReleaseType); 436 Region->MMLock.unlock(); 437 return BytesReleased; 438 } 439 return 0; 440 } 441 442 uptr releaseToOS(ReleaseToOS ReleaseType) { 443 uptr TotalReleasedBytes = 0; 444 for (uptr I = 0; I < NumClasses; I++) { 445 if (I == SizeClassMap::BatchClassId) 446 continue; 447 RegionInfo *Region = getRegionInfo(I); 448 ScopedLock L(Region->MMLock); 449 TotalReleasedBytes += releaseToOSMaybe(Region, I, ReleaseType); 450 } 451 return TotalReleasedBytes; 452 } 453 454 const char *getRegionInfoArrayAddress() const { 455 return reinterpret_cast<const char *>(RegionInfoArray); 456 } 457 458 static uptr getRegionInfoArraySize() { return sizeof(RegionInfoArray); } 459 460 uptr getCompactPtrBaseByClassId(uptr ClassId) { 461 return getRegionInfo(ClassId)->RegionBeg; 462 } 463 464 CompactPtrT compactPtr(uptr ClassId, uptr Ptr) { 465 DCHECK_LE(ClassId, SizeClassMap::LargestClassId); 466 return compactPtrInternal(getCompactPtrBaseByClassId(ClassId), Ptr); 467 } 468 469 void *decompactPtr(uptr ClassId, CompactPtrT CompactPtr) { 470 DCHECK_LE(ClassId, SizeClassMap::LargestClassId); 471 return reinterpret_cast<void *>( 472 decompactPtrInternal(getCompactPtrBaseByClassId(ClassId), CompactPtr)); 473 } 474 475 static BlockInfo findNearestBlock(const char *RegionInfoData, 476 uptr Ptr) NO_THREAD_SAFETY_ANALYSIS { 477 const RegionInfo *RegionInfoArray = 478 reinterpret_cast<const RegionInfo *>(RegionInfoData); 479 480 uptr ClassId; 481 uptr MinDistance = -1UL; 482 for (uptr I = 0; I != NumClasses; ++I) { 483 if (I == SizeClassMap::BatchClassId) 484 continue; 485 uptr Begin = RegionInfoArray[I].RegionBeg; 486 // TODO(chiahungduan): In fact, We need to lock the RegionInfo::MMLock. 487 // However, the RegionInfoData is passed with const qualifier and lock the 488 // mutex requires modifying RegionInfoData, which means we need to remove 489 // the const qualifier. This may lead to another undefined behavior (The 490 // first one is accessing `AllocatedUser` without locking. It's better to 491 // pass `RegionInfoData` as `void *` then we can lock the mutex properly. 492 uptr End = Begin + RegionInfoArray[I].MemMapInfo.AllocatedUser; 493 if (Begin > End || End - Begin < SizeClassMap::getSizeByClassId(I)) 494 continue; 495 uptr RegionDistance; 496 if (Begin <= Ptr) { 497 if (Ptr < End) 498 RegionDistance = 0; 499 else 500 RegionDistance = Ptr - End; 501 } else { 502 RegionDistance = Begin - Ptr; 503 } 504 505 if (RegionDistance < MinDistance) { 506 MinDistance = RegionDistance; 507 ClassId = I; 508 } 509 } 510 511 BlockInfo B = {}; 512 if (MinDistance <= 8192) { 513 B.RegionBegin = RegionInfoArray[ClassId].RegionBeg; 514 B.RegionEnd = 515 B.RegionBegin + RegionInfoArray[ClassId].MemMapInfo.AllocatedUser; 516 B.BlockSize = SizeClassMap::getSizeByClassId(ClassId); 517 B.BlockBegin = 518 B.RegionBegin + uptr(sptr(Ptr - B.RegionBegin) / sptr(B.BlockSize) * 519 sptr(B.BlockSize)); 520 while (B.BlockBegin < B.RegionBegin) 521 B.BlockBegin += B.BlockSize; 522 while (B.RegionEnd < B.BlockBegin + B.BlockSize) 523 B.BlockBegin -= B.BlockSize; 524 } 525 return B; 526 } 527 528 AtomicOptions Options; 529 530 private: 531 static const uptr RegionSize = 1UL << RegionSizeLog; 532 static const uptr NumClasses = SizeClassMap::NumClasses; 533 static const uptr PrimarySize = RegionSize * NumClasses; 534 535 static const uptr MapSizeIncrement = Config::Primary::MapSizeIncrement; 536 // Fill at most this number of batches from the newly map'd memory. 537 static const u32 MaxNumBatches = SCUDO_ANDROID ? 4U : 8U; 538 539 struct ReleaseToOsInfo { 540 uptr BytesInFreeListAtLastCheckpoint; 541 uptr RangesReleased; 542 uptr LastReleasedBytes; 543 u64 LastReleaseAtNs; 544 }; 545 546 struct BlocksInfo { 547 SinglyLinkedList<BatchGroupT> BlockList = {}; 548 uptr PoppedBlocks = 0; 549 uptr PushedBlocks = 0; 550 }; 551 552 struct PagesInfo { 553 MemMapT MemMap = {}; 554 // Bytes mapped for user memory. 555 uptr MappedUser = 0; 556 // Bytes allocated for user memory. 557 uptr AllocatedUser = 0; 558 }; 559 560 struct UnpaddedRegionInfo { 561 // Mutex for operations on freelist 562 HybridMutex FLLock; 563 ConditionVariableT FLLockCV GUARDED_BY(FLLock); 564 // Mutex for memmap operations 565 HybridMutex MMLock ACQUIRED_BEFORE(FLLock); 566 // `RegionBeg` is initialized before thread creation and won't be changed. 567 uptr RegionBeg = 0; 568 u32 RandState GUARDED_BY(MMLock) = 0; 569 BlocksInfo FreeListInfo GUARDED_BY(FLLock); 570 PagesInfo MemMapInfo GUARDED_BY(MMLock); 571 // The minimum size of pushed blocks to trigger page release. 572 uptr TryReleaseThreshold GUARDED_BY(MMLock) = 0; 573 ReleaseToOsInfo ReleaseInfo GUARDED_BY(MMLock) = {}; 574 bool Exhausted GUARDED_BY(MMLock) = false; 575 bool isPopulatingFreeList GUARDED_BY(FLLock) = false; 576 }; 577 struct RegionInfo : UnpaddedRegionInfo { 578 char Padding[SCUDO_CACHE_LINE_SIZE - 579 (sizeof(UnpaddedRegionInfo) % SCUDO_CACHE_LINE_SIZE)] = {}; 580 }; 581 static_assert(sizeof(RegionInfo) % SCUDO_CACHE_LINE_SIZE == 0, ""); 582 583 RegionInfo *getRegionInfo(uptr ClassId) { 584 DCHECK_LT(ClassId, NumClasses); 585 return &RegionInfoArray[ClassId]; 586 } 587 588 uptr getRegionBaseByClassId(uptr ClassId) { 589 return roundDown(getRegionInfo(ClassId)->RegionBeg - PrimaryBase, 590 RegionSize) + 591 PrimaryBase; 592 } 593 594 static CompactPtrT compactPtrInternal(uptr Base, uptr Ptr) { 595 return static_cast<CompactPtrT>((Ptr - Base) >> CompactPtrScale); 596 } 597 598 static uptr decompactPtrInternal(uptr Base, CompactPtrT CompactPtr) { 599 return Base + (static_cast<uptr>(CompactPtr) << CompactPtrScale); 600 } 601 602 static uptr compactPtrGroup(CompactPtrT CompactPtr) { 603 const uptr Mask = (static_cast<uptr>(1) << GroupScale) - 1; 604 return static_cast<uptr>(CompactPtr) & ~Mask; 605 } 606 static uptr decompactGroupBase(uptr Base, uptr CompactPtrGroupBase) { 607 DCHECK_EQ(CompactPtrGroupBase % (static_cast<uptr>(1) << (GroupScale)), 0U); 608 return Base + (CompactPtrGroupBase << CompactPtrScale); 609 } 610 611 ALWAYS_INLINE static bool isSmallBlock(uptr BlockSize) { 612 const uptr PageSize = getPageSizeCached(); 613 return BlockSize < PageSize / 16U; 614 } 615 616 ALWAYS_INLINE static bool isLargeBlock(uptr BlockSize) { 617 const uptr PageSize = getPageSizeCached(); 618 return BlockSize > PageSize; 619 } 620 621 void pushBatchClassBlocks(RegionInfo *Region, CompactPtrT *Array, u32 Size) 622 REQUIRES(Region->FLLock) { 623 DCHECK_EQ(Region, getRegionInfo(SizeClassMap::BatchClassId)); 624 625 // Free blocks are recorded by TransferBatch in freelist for all 626 // size-classes. In addition, TransferBatch is allocated from BatchClassId. 627 // In order not to use additional block to record the free blocks in 628 // BatchClassId, they are self-contained. I.e., A TransferBatch records the 629 // block address of itself. See the figure below: 630 // 631 // TransferBatch at 0xABCD 632 // +----------------------------+ 633 // | Free blocks' addr | 634 // | +------+------+------+ | 635 // | |0xABCD|... |... | | 636 // | +------+------+------+ | 637 // +----------------------------+ 638 // 639 // When we allocate all the free blocks in the TransferBatch, the block used 640 // by TransferBatch is also free for use. We don't need to recycle the 641 // TransferBatch. Note that the correctness is maintained by the invariant, 642 // 643 // The unit of each popBatch() request is entire TransferBatch. Return 644 // part of the blocks in a TransferBatch is invalid. 645 // 646 // This ensures that TransferBatch won't leak the address itself while it's 647 // still holding other valid data. 648 // 649 // Besides, BatchGroup is also allocated from BatchClassId and has its 650 // address recorded in the TransferBatch too. To maintain the correctness, 651 // 652 // The address of BatchGroup is always recorded in the last TransferBatch 653 // in the freelist (also imply that the freelist should only be 654 // updated with push_front). Once the last TransferBatch is popped, 655 // the block used by BatchGroup is also free for use. 656 // 657 // With this approach, the blocks used by BatchGroup and TransferBatch are 658 // reusable and don't need additional space for them. 659 660 Region->FreeListInfo.PushedBlocks += Size; 661 BatchGroupT *BG = Region->FreeListInfo.BlockList.front(); 662 663 if (BG == nullptr) { 664 // Construct `BatchGroup` on the last element. 665 BG = reinterpret_cast<BatchGroupT *>( 666 decompactPtr(SizeClassMap::BatchClassId, Array[Size - 1])); 667 --Size; 668 BG->Batches.clear(); 669 // BatchClass hasn't enabled memory group. Use `0` to indicate there's no 670 // memory group here. 671 BG->CompactPtrGroupBase = 0; 672 // `BG` is also the block of BatchClassId. Note that this is different 673 // from `CreateGroup` in `pushBlocksImpl` 674 BG->PushedBlocks = 1; 675 BG->BytesInBGAtLastCheckpoint = 0; 676 BG->MaxCachedPerBatch = 677 CacheT::getMaxCached(getSizeByClassId(SizeClassMap::BatchClassId)); 678 679 Region->FreeListInfo.BlockList.push_front(BG); 680 } 681 682 if (UNLIKELY(Size == 0)) 683 return; 684 685 // This happens under 2 cases. 686 // 1. just allocated a new `BatchGroup`. 687 // 2. Only 1 block is pushed when the freelist is empty. 688 if (BG->Batches.empty()) { 689 // Construct the `TransferBatch` on the last element. 690 TransferBatchT *TB = reinterpret_cast<TransferBatchT *>( 691 decompactPtr(SizeClassMap::BatchClassId, Array[Size - 1])); 692 TB->clear(); 693 // As mentioned above, addresses of `TransferBatch` and `BatchGroup` are 694 // recorded in the TransferBatch. 695 TB->add(Array[Size - 1]); 696 TB->add( 697 compactPtr(SizeClassMap::BatchClassId, reinterpret_cast<uptr>(BG))); 698 --Size; 699 DCHECK_EQ(BG->PushedBlocks, 1U); 700 // `TB` is also the block of BatchClassId. 701 BG->PushedBlocks += 1; 702 BG->Batches.push_front(TB); 703 } 704 705 TransferBatchT *CurBatch = BG->Batches.front(); 706 DCHECK_NE(CurBatch, nullptr); 707 708 for (u32 I = 0; I < Size;) { 709 u16 UnusedSlots = 710 static_cast<u16>(BG->MaxCachedPerBatch - CurBatch->getCount()); 711 if (UnusedSlots == 0) { 712 CurBatch = reinterpret_cast<TransferBatchT *>( 713 decompactPtr(SizeClassMap::BatchClassId, Array[I])); 714 CurBatch->clear(); 715 // Self-contained 716 CurBatch->add(Array[I]); 717 ++I; 718 // TODO(chiahungduan): Avoid the use of push_back() in `Batches` of 719 // BatchClassId. 720 BG->Batches.push_front(CurBatch); 721 UnusedSlots = static_cast<u16>(BG->MaxCachedPerBatch - 1); 722 } 723 // `UnusedSlots` is u16 so the result will be also fit in u16. 724 const u16 AppendSize = static_cast<u16>(Min<u32>(UnusedSlots, Size - I)); 725 CurBatch->appendFromArray(&Array[I], AppendSize); 726 I += AppendSize; 727 } 728 729 BG->PushedBlocks += Size; 730 } 731 732 // Push the blocks to their batch group. The layout will be like, 733 // 734 // FreeListInfo.BlockList - > BG -> BG -> BG 735 // | | | 736 // v v v 737 // TB TB TB 738 // | 739 // v 740 // TB 741 // 742 // Each BlockGroup(BG) will associate with unique group id and the free blocks 743 // are managed by a list of TransferBatch(TB). To reduce the time of inserting 744 // blocks, BGs are sorted and the input `Array` are supposed to be sorted so 745 // that we can get better performance of maintaining sorted property. 746 // Use `SameGroup=true` to indicate that all blocks in the array are from the 747 // same group then we will skip checking the group id of each block. 748 void pushBlocksImpl(CacheT *C, uptr ClassId, RegionInfo *Region, 749 CompactPtrT *Array, u32 Size, bool SameGroup = false) 750 REQUIRES(Region->FLLock) { 751 DCHECK_NE(ClassId, SizeClassMap::BatchClassId); 752 DCHECK_GT(Size, 0U); 753 754 auto CreateGroup = [&](uptr CompactPtrGroupBase) { 755 BatchGroupT *BG = 756 reinterpret_cast<BatchGroupT *>(C->getBatchClassBlock()); 757 BG->Batches.clear(); 758 TransferBatchT *TB = 759 reinterpret_cast<TransferBatchT *>(C->getBatchClassBlock()); 760 TB->clear(); 761 762 BG->CompactPtrGroupBase = CompactPtrGroupBase; 763 BG->Batches.push_front(TB); 764 BG->PushedBlocks = 0; 765 BG->BytesInBGAtLastCheckpoint = 0; 766 BG->MaxCachedPerBatch = CacheT::getMaxCached(getSizeByClassId(ClassId)); 767 768 return BG; 769 }; 770 771 auto InsertBlocks = [&](BatchGroupT *BG, CompactPtrT *Array, u32 Size) { 772 SinglyLinkedList<TransferBatchT> &Batches = BG->Batches; 773 TransferBatchT *CurBatch = Batches.front(); 774 DCHECK_NE(CurBatch, nullptr); 775 776 for (u32 I = 0; I < Size;) { 777 DCHECK_GE(BG->MaxCachedPerBatch, CurBatch->getCount()); 778 u16 UnusedSlots = 779 static_cast<u16>(BG->MaxCachedPerBatch - CurBatch->getCount()); 780 if (UnusedSlots == 0) { 781 CurBatch = 782 reinterpret_cast<TransferBatchT *>(C->getBatchClassBlock()); 783 CurBatch->clear(); 784 Batches.push_front(CurBatch); 785 UnusedSlots = BG->MaxCachedPerBatch; 786 } 787 // `UnusedSlots` is u16 so the result will be also fit in u16. 788 u16 AppendSize = static_cast<u16>(Min<u32>(UnusedSlots, Size - I)); 789 CurBatch->appendFromArray(&Array[I], AppendSize); 790 I += AppendSize; 791 } 792 793 BG->PushedBlocks += Size; 794 }; 795 796 Region->FreeListInfo.PushedBlocks += Size; 797 BatchGroupT *Cur = Region->FreeListInfo.BlockList.front(); 798 799 // In the following, `Cur` always points to the BatchGroup for blocks that 800 // will be pushed next. `Prev` is the element right before `Cur`. 801 BatchGroupT *Prev = nullptr; 802 803 while (Cur != nullptr && 804 compactPtrGroup(Array[0]) > Cur->CompactPtrGroupBase) { 805 Prev = Cur; 806 Cur = Cur->Next; 807 } 808 809 if (Cur == nullptr || 810 compactPtrGroup(Array[0]) != Cur->CompactPtrGroupBase) { 811 Cur = CreateGroup(compactPtrGroup(Array[0])); 812 if (Prev == nullptr) 813 Region->FreeListInfo.BlockList.push_front(Cur); 814 else 815 Region->FreeListInfo.BlockList.insert(Prev, Cur); 816 } 817 818 // All the blocks are from the same group, just push without checking group 819 // id. 820 if (SameGroup) { 821 for (u32 I = 0; I < Size; ++I) 822 DCHECK_EQ(compactPtrGroup(Array[I]), Cur->CompactPtrGroupBase); 823 824 InsertBlocks(Cur, Array, Size); 825 return; 826 } 827 828 // The blocks are sorted by group id. Determine the segment of group and 829 // push them to their group together. 830 u32 Count = 1; 831 for (u32 I = 1; I < Size; ++I) { 832 if (compactPtrGroup(Array[I - 1]) != compactPtrGroup(Array[I])) { 833 DCHECK_EQ(compactPtrGroup(Array[I - 1]), Cur->CompactPtrGroupBase); 834 InsertBlocks(Cur, Array + I - Count, Count); 835 836 while (Cur != nullptr && 837 compactPtrGroup(Array[I]) > Cur->CompactPtrGroupBase) { 838 Prev = Cur; 839 Cur = Cur->Next; 840 } 841 842 if (Cur == nullptr || 843 compactPtrGroup(Array[I]) != Cur->CompactPtrGroupBase) { 844 Cur = CreateGroup(compactPtrGroup(Array[I])); 845 DCHECK_NE(Prev, nullptr); 846 Region->FreeListInfo.BlockList.insert(Prev, Cur); 847 } 848 849 Count = 1; 850 } else { 851 ++Count; 852 } 853 } 854 855 InsertBlocks(Cur, Array + Size - Count, Count); 856 } 857 858 TransferBatchT *popBatchWithCV(CacheT *C, uptr ClassId, RegionInfo *Region, 859 bool &ReportRegionExhausted) { 860 TransferBatchT *B = nullptr; 861 862 while (true) { 863 // We only expect one thread doing the freelist refillment and other 864 // threads will be waiting for either the completion of the 865 // `populateFreeListAndPopBatch()` or `pushBlocks()` called by other 866 // threads. 867 bool PopulateFreeList = false; 868 { 869 ScopedLock FL(Region->FLLock); 870 if (!Region->isPopulatingFreeList) { 871 Region->isPopulatingFreeList = true; 872 PopulateFreeList = true; 873 } 874 } 875 876 if (PopulateFreeList) { 877 ScopedLock ML(Region->MMLock); 878 879 const bool RegionIsExhausted = Region->Exhausted; 880 if (!RegionIsExhausted) 881 B = populateFreeListAndPopBatch(C, ClassId, Region); 882 ReportRegionExhausted = !RegionIsExhausted && Region->Exhausted; 883 884 { 885 // Before reacquiring the `FLLock`, the freelist may be used up again 886 // and some threads are waiting for the freelist refillment by the 887 // current thread. It's important to set 888 // `Region->isPopulatingFreeList` to false so the threads about to 889 // sleep will notice the status change. 890 ScopedLock FL(Region->FLLock); 891 Region->isPopulatingFreeList = false; 892 Region->FLLockCV.notifyAll(Region->FLLock); 893 } 894 895 break; 896 } 897 898 // At here, there are two preconditions to be met before waiting, 899 // 1. The freelist is empty. 900 // 2. Region->isPopulatingFreeList == true, i.e, someone is still doing 901 // `populateFreeListAndPopBatch()`. 902 // 903 // Note that it has the chance that freelist is empty but 904 // Region->isPopulatingFreeList == false because all the new populated 905 // blocks were used up right after the refillment. Therefore, we have to 906 // check if someone is still populating the freelist. 907 ScopedLock FL(Region->FLLock); 908 if (LIKELY(B = popBatchImpl(C, ClassId, Region))) 909 break; 910 911 if (!Region->isPopulatingFreeList) 912 continue; 913 914 // Now the freelist is empty and someone's doing the refillment. We will 915 // wait until anyone refills the freelist or someone finishes doing 916 // `populateFreeListAndPopBatch()`. The refillment can be done by 917 // `populateFreeListAndPopBatch()`, `pushBlocks()`, 918 // `pushBatchClassBlocks()` and `mergeGroupsToReleaseBack()`. 919 Region->FLLockCV.wait(Region->FLLock); 920 921 if (LIKELY(B = popBatchImpl(C, ClassId, Region))) 922 break; 923 } 924 925 return B; 926 } 927 928 // Pop one TransferBatch from a BatchGroup. The BatchGroup with the smallest 929 // group id will be considered first. 930 // 931 // The region mutex needs to be held while calling this method. 932 TransferBatchT *popBatchImpl(CacheT *C, uptr ClassId, RegionInfo *Region) 933 REQUIRES(Region->FLLock) { 934 if (Region->FreeListInfo.BlockList.empty()) 935 return nullptr; 936 937 SinglyLinkedList<TransferBatchT> &Batches = 938 Region->FreeListInfo.BlockList.front()->Batches; 939 940 if (Batches.empty()) { 941 DCHECK_EQ(ClassId, SizeClassMap::BatchClassId); 942 BatchGroupT *BG = Region->FreeListInfo.BlockList.front(); 943 Region->FreeListInfo.BlockList.pop_front(); 944 945 // Block used by `BatchGroup` is from BatchClassId. Turn the block into 946 // `TransferBatch` with single block. 947 TransferBatchT *TB = reinterpret_cast<TransferBatchT *>(BG); 948 TB->clear(); 949 TB->add( 950 compactPtr(SizeClassMap::BatchClassId, reinterpret_cast<uptr>(TB))); 951 Region->FreeListInfo.PoppedBlocks += 1; 952 return TB; 953 } 954 955 TransferBatchT *B = Batches.front(); 956 Batches.pop_front(); 957 DCHECK_NE(B, nullptr); 958 DCHECK_GT(B->getCount(), 0U); 959 960 if (Batches.empty()) { 961 BatchGroupT *BG = Region->FreeListInfo.BlockList.front(); 962 Region->FreeListInfo.BlockList.pop_front(); 963 964 // We don't keep BatchGroup with zero blocks to avoid empty-checking while 965 // allocating. Note that block used by constructing BatchGroup is recorded 966 // as free blocks in the last element of BatchGroup::Batches. Which means, 967 // once we pop the last TransferBatch, the block is implicitly 968 // deallocated. 969 if (ClassId != SizeClassMap::BatchClassId) 970 C->deallocate(SizeClassMap::BatchClassId, BG); 971 } 972 973 Region->FreeListInfo.PoppedBlocks += B->getCount(); 974 975 return B; 976 } 977 978 // Refill the freelist and return one batch. 979 NOINLINE TransferBatchT *populateFreeListAndPopBatch(CacheT *C, uptr ClassId, 980 RegionInfo *Region) 981 REQUIRES(Region->MMLock) EXCLUDES(Region->FLLock) { 982 const uptr Size = getSizeByClassId(ClassId); 983 const u16 MaxCount = CacheT::getMaxCached(Size); 984 985 const uptr RegionBeg = Region->RegionBeg; 986 const uptr MappedUser = Region->MemMapInfo.MappedUser; 987 const uptr TotalUserBytes = 988 Region->MemMapInfo.AllocatedUser + MaxCount * Size; 989 // Map more space for blocks, if necessary. 990 if (TotalUserBytes > MappedUser) { 991 // Do the mmap for the user memory. 992 const uptr MapSize = 993 roundUp(TotalUserBytes - MappedUser, MapSizeIncrement); 994 const uptr RegionBase = RegionBeg - getRegionBaseByClassId(ClassId); 995 if (UNLIKELY(RegionBase + MappedUser + MapSize > RegionSize)) { 996 Region->Exhausted = true; 997 return nullptr; 998 } 999 1000 if (UNLIKELY(!Region->MemMapInfo.MemMap.remap( 1001 RegionBeg + MappedUser, MapSize, "scudo:primary", 1002 MAP_ALLOWNOMEM | MAP_RESIZABLE | 1003 (useMemoryTagging<Config>(Options.load()) ? MAP_MEMTAG 1004 : 0)))) { 1005 return nullptr; 1006 } 1007 Region->MemMapInfo.MappedUser += MapSize; 1008 C->getStats().add(StatMapped, MapSize); 1009 } 1010 1011 const u32 NumberOfBlocks = 1012 Min(MaxNumBatches * MaxCount, 1013 static_cast<u32>((Region->MemMapInfo.MappedUser - 1014 Region->MemMapInfo.AllocatedUser) / 1015 Size)); 1016 DCHECK_GT(NumberOfBlocks, 0); 1017 1018 constexpr u32 ShuffleArraySize = 1019 MaxNumBatches * TransferBatchT::MaxNumCached; 1020 CompactPtrT ShuffleArray[ShuffleArraySize]; 1021 DCHECK_LE(NumberOfBlocks, ShuffleArraySize); 1022 1023 const uptr CompactPtrBase = getCompactPtrBaseByClassId(ClassId); 1024 uptr P = RegionBeg + Region->MemMapInfo.AllocatedUser; 1025 for (u32 I = 0; I < NumberOfBlocks; I++, P += Size) 1026 ShuffleArray[I] = compactPtrInternal(CompactPtrBase, P); 1027 1028 ScopedLock L(Region->FLLock); 1029 1030 if (ClassId != SizeClassMap::BatchClassId) { 1031 u32 N = 1; 1032 uptr CurGroup = compactPtrGroup(ShuffleArray[0]); 1033 for (u32 I = 1; I < NumberOfBlocks; I++) { 1034 if (UNLIKELY(compactPtrGroup(ShuffleArray[I]) != CurGroup)) { 1035 shuffle(ShuffleArray + I - N, N, &Region->RandState); 1036 pushBlocksImpl(C, ClassId, Region, ShuffleArray + I - N, N, 1037 /*SameGroup=*/true); 1038 N = 1; 1039 CurGroup = compactPtrGroup(ShuffleArray[I]); 1040 } else { 1041 ++N; 1042 } 1043 } 1044 1045 shuffle(ShuffleArray + NumberOfBlocks - N, N, &Region->RandState); 1046 pushBlocksImpl(C, ClassId, Region, &ShuffleArray[NumberOfBlocks - N], N, 1047 /*SameGroup=*/true); 1048 } else { 1049 pushBatchClassBlocks(Region, ShuffleArray, NumberOfBlocks); 1050 } 1051 1052 TransferBatchT *B = popBatchImpl(C, ClassId, Region); 1053 DCHECK_NE(B, nullptr); 1054 1055 // Note that `PushedBlocks` and `PoppedBlocks` are supposed to only record 1056 // the requests from `PushBlocks` and `PopBatch` which are external 1057 // interfaces. `populateFreeListAndPopBatch` is the internal interface so we 1058 // should set the values back to avoid incorrectly setting the stats. 1059 Region->FreeListInfo.PushedBlocks -= NumberOfBlocks; 1060 1061 const uptr AllocatedUser = Size * NumberOfBlocks; 1062 C->getStats().add(StatFree, AllocatedUser); 1063 Region->MemMapInfo.AllocatedUser += AllocatedUser; 1064 1065 return B; 1066 } 1067 1068 void getStats(ScopedString *Str, uptr ClassId, RegionInfo *Region) 1069 REQUIRES(Region->MMLock, Region->FLLock) { 1070 if (Region->MemMapInfo.MappedUser == 0) 1071 return; 1072 const uptr BlockSize = getSizeByClassId(ClassId); 1073 const uptr InUseBlocks = 1074 Region->FreeListInfo.PoppedBlocks - Region->FreeListInfo.PushedBlocks; 1075 const uptr BytesInFreeList = 1076 Region->MemMapInfo.AllocatedUser - InUseBlocks * BlockSize; 1077 uptr RegionPushedBytesDelta = 0; 1078 if (BytesInFreeList >= 1079 Region->ReleaseInfo.BytesInFreeListAtLastCheckpoint) { 1080 RegionPushedBytesDelta = 1081 BytesInFreeList - Region->ReleaseInfo.BytesInFreeListAtLastCheckpoint; 1082 } 1083 const uptr TotalChunks = Region->MemMapInfo.AllocatedUser / BlockSize; 1084 Str->append( 1085 "%s %02zu (%6zu): mapped: %6zuK popped: %7zu pushed: %7zu " 1086 "inuse: %6zu total: %6zu releases: %6zu last " 1087 "released: %6zuK latest pushed bytes: %6zuK region: 0x%zx (0x%zx)\n", 1088 Region->Exhausted ? "E" : " ", ClassId, getSizeByClassId(ClassId), 1089 Region->MemMapInfo.MappedUser >> 10, Region->FreeListInfo.PoppedBlocks, 1090 Region->FreeListInfo.PushedBlocks, InUseBlocks, TotalChunks, 1091 Region->ReleaseInfo.RangesReleased, 1092 Region->ReleaseInfo.LastReleasedBytes >> 10, 1093 RegionPushedBytesDelta >> 10, Region->RegionBeg, 1094 getRegionBaseByClassId(ClassId)); 1095 } 1096 1097 void getRegionFragmentationInfo(RegionInfo *Region, uptr ClassId, 1098 ScopedString *Str) REQUIRES(Region->MMLock) { 1099 const uptr BlockSize = getSizeByClassId(ClassId); 1100 const uptr AllocatedUserEnd = 1101 Region->MemMapInfo.AllocatedUser + Region->RegionBeg; 1102 1103 SinglyLinkedList<BatchGroupT> GroupsToRelease; 1104 { 1105 ScopedLock L(Region->FLLock); 1106 GroupsToRelease = Region->FreeListInfo.BlockList; 1107 Region->FreeListInfo.BlockList.clear(); 1108 } 1109 1110 FragmentationRecorder Recorder; 1111 if (!GroupsToRelease.empty()) { 1112 PageReleaseContext Context = 1113 markFreeBlocks(Region, BlockSize, AllocatedUserEnd, 1114 getCompactPtrBaseByClassId(ClassId), GroupsToRelease); 1115 auto SkipRegion = [](UNUSED uptr RegionIndex) { return false; }; 1116 releaseFreeMemoryToOS(Context, Recorder, SkipRegion); 1117 1118 mergeGroupsToReleaseBack(Region, GroupsToRelease); 1119 } 1120 1121 ScopedLock L(Region->FLLock); 1122 const uptr PageSize = getPageSizeCached(); 1123 const uptr TotalBlocks = Region->MemMapInfo.AllocatedUser / BlockSize; 1124 const uptr InUseBlocks = 1125 Region->FreeListInfo.PoppedBlocks - Region->FreeListInfo.PushedBlocks; 1126 const uptr AllocatedPagesCount = 1127 roundUp(Region->MemMapInfo.AllocatedUser, PageSize) / PageSize; 1128 DCHECK_GE(AllocatedPagesCount, Recorder.getReleasedPagesCount()); 1129 const uptr InUsePages = 1130 AllocatedPagesCount - Recorder.getReleasedPagesCount(); 1131 const uptr InUseBytes = InUsePages * PageSize; 1132 1133 uptr Integral; 1134 uptr Fractional; 1135 computePercentage(BlockSize * InUseBlocks, InUsePages * PageSize, &Integral, 1136 &Fractional); 1137 Str->append(" %02zu (%6zu): inuse/total blocks: %6zu/%6zu inuse/total " 1138 "pages: %6zu/%6zu inuse bytes: %6zuK util: %3zu.%02zu%%\n", 1139 ClassId, BlockSize, InUseBlocks, TotalBlocks, InUsePages, 1140 AllocatedPagesCount, InUseBytes >> 10, Integral, Fractional); 1141 } 1142 1143 NOINLINE uptr releaseToOSMaybe(RegionInfo *Region, uptr ClassId, 1144 ReleaseToOS ReleaseType = ReleaseToOS::Normal) 1145 REQUIRES(Region->MMLock) EXCLUDES(Region->FLLock) { 1146 const uptr BlockSize = getSizeByClassId(ClassId); 1147 uptr BytesInFreeList; 1148 const uptr AllocatedUserEnd = 1149 Region->MemMapInfo.AllocatedUser + Region->RegionBeg; 1150 SinglyLinkedList<BatchGroupT> GroupsToRelease; 1151 1152 { 1153 ScopedLock L(Region->FLLock); 1154 1155 BytesInFreeList = Region->MemMapInfo.AllocatedUser - 1156 (Region->FreeListInfo.PoppedBlocks - 1157 Region->FreeListInfo.PushedBlocks) * 1158 BlockSize; 1159 if (UNLIKELY(BytesInFreeList == 0)) 1160 return false; 1161 1162 // ==================================================================== // 1163 // 1. Check if we have enough free blocks and if it's worth doing a page 1164 // release. 1165 // ==================================================================== // 1166 if (ReleaseType != ReleaseToOS::ForceAll && 1167 !hasChanceToReleasePages(Region, BlockSize, BytesInFreeList, 1168 ReleaseType)) { 1169 return 0; 1170 } 1171 1172 // ==================================================================== // 1173 // 2. Determine which groups can release the pages. Use a heuristic to 1174 // gather groups that are candidates for doing a release. 1175 // ==================================================================== // 1176 if (ReleaseType == ReleaseToOS::ForceAll) { 1177 GroupsToRelease = Region->FreeListInfo.BlockList; 1178 Region->FreeListInfo.BlockList.clear(); 1179 } else { 1180 GroupsToRelease = 1181 collectGroupsToRelease(Region, BlockSize, AllocatedUserEnd, 1182 getCompactPtrBaseByClassId(ClassId)); 1183 } 1184 if (GroupsToRelease.empty()) 1185 return 0; 1186 } 1187 1188 // Note that we have extracted the `GroupsToRelease` from region freelist. 1189 // It's safe to let pushBlocks()/popBatches() access the remaining region 1190 // freelist. In the steps 3 and 4, we will temporarily release the FLLock 1191 // and lock it again before step 5. 1192 1193 // ==================================================================== // 1194 // 3. Mark the free blocks in `GroupsToRelease` in the `PageReleaseContext`. 1195 // Then we can tell which pages are in-use by querying 1196 // `PageReleaseContext`. 1197 // ==================================================================== // 1198 PageReleaseContext Context = 1199 markFreeBlocks(Region, BlockSize, AllocatedUserEnd, 1200 getCompactPtrBaseByClassId(ClassId), GroupsToRelease); 1201 if (UNLIKELY(!Context.hasBlockMarked())) { 1202 mergeGroupsToReleaseBack(Region, GroupsToRelease); 1203 return 0; 1204 } 1205 1206 // ==================================================================== // 1207 // 4. Release the unused physical pages back to the OS. 1208 // ==================================================================== // 1209 RegionReleaseRecorder<MemMapT> Recorder(&Region->MemMapInfo.MemMap, 1210 Region->RegionBeg, 1211 Context.getReleaseOffset()); 1212 auto SkipRegion = [](UNUSED uptr RegionIndex) { return false; }; 1213 releaseFreeMemoryToOS(Context, Recorder, SkipRegion); 1214 if (Recorder.getReleasedRangesCount() > 0) { 1215 Region->ReleaseInfo.BytesInFreeListAtLastCheckpoint = BytesInFreeList; 1216 Region->ReleaseInfo.RangesReleased += Recorder.getReleasedRangesCount(); 1217 Region->ReleaseInfo.LastReleasedBytes = Recorder.getReleasedBytes(); 1218 } 1219 Region->ReleaseInfo.LastReleaseAtNs = getMonotonicTimeFast(); 1220 1221 // ====================================================================== // 1222 // 5. Merge the `GroupsToRelease` back to the freelist. 1223 // ====================================================================== // 1224 mergeGroupsToReleaseBack(Region, GroupsToRelease); 1225 1226 return Recorder.getReleasedBytes(); 1227 } 1228 1229 bool hasChanceToReleasePages(RegionInfo *Region, uptr BlockSize, 1230 uptr BytesInFreeList, ReleaseToOS ReleaseType) 1231 REQUIRES(Region->MMLock, Region->FLLock) { 1232 DCHECK_GE(Region->FreeListInfo.PoppedBlocks, 1233 Region->FreeListInfo.PushedBlocks); 1234 const uptr PageSize = getPageSizeCached(); 1235 1236 // Always update `BytesInFreeListAtLastCheckpoint` with the smallest value 1237 // so that we won't underestimate the releasable pages. For example, the 1238 // following is the region usage, 1239 // 1240 // BytesInFreeListAtLastCheckpoint AllocatedUser 1241 // v v 1242 // |---------------------------------------> 1243 // ^ ^ 1244 // BytesInFreeList ReleaseThreshold 1245 // 1246 // In general, if we have collected enough bytes and the amount of free 1247 // bytes meets the ReleaseThreshold, we will try to do page release. If we 1248 // don't update `BytesInFreeListAtLastCheckpoint` when the current 1249 // `BytesInFreeList` is smaller, we may take longer time to wait for enough 1250 // freed blocks because we miss the bytes between 1251 // (BytesInFreeListAtLastCheckpoint - BytesInFreeList). 1252 if (BytesInFreeList <= 1253 Region->ReleaseInfo.BytesInFreeListAtLastCheckpoint) { 1254 Region->ReleaseInfo.BytesInFreeListAtLastCheckpoint = BytesInFreeList; 1255 } 1256 1257 const uptr RegionPushedBytesDelta = 1258 BytesInFreeList - Region->ReleaseInfo.BytesInFreeListAtLastCheckpoint; 1259 if (RegionPushedBytesDelta < PageSize) 1260 return false; 1261 1262 // Releasing smaller blocks is expensive, so we want to make sure that a 1263 // significant amount of bytes are free, and that there has been a good 1264 // amount of batches pushed to the freelist before attempting to release. 1265 if (isSmallBlock(BlockSize) && ReleaseType == ReleaseToOS::Normal) 1266 if (RegionPushedBytesDelta < Region->TryReleaseThreshold) 1267 return false; 1268 1269 if (ReleaseType == ReleaseToOS::Normal) { 1270 const s32 IntervalMs = atomic_load_relaxed(&ReleaseToOsIntervalMs); 1271 if (IntervalMs < 0) 1272 return false; 1273 1274 // The constant 8 here is selected from profiling some apps and the number 1275 // of unreleased pages in the large size classes is around 16 pages or 1276 // more. Choose half of it as a heuristic and which also avoids page 1277 // release every time for every pushBlocks() attempt by large blocks. 1278 const bool ByPassReleaseInterval = 1279 isLargeBlock(BlockSize) && RegionPushedBytesDelta > 8 * PageSize; 1280 if (!ByPassReleaseInterval) { 1281 if (Region->ReleaseInfo.LastReleaseAtNs + 1282 static_cast<u64>(IntervalMs) * 1000000 > 1283 getMonotonicTimeFast()) { 1284 // Memory was returned recently. 1285 return false; 1286 } 1287 } 1288 } // if (ReleaseType == ReleaseToOS::Normal) 1289 1290 return true; 1291 } 1292 1293 SinglyLinkedList<BatchGroupT> 1294 collectGroupsToRelease(RegionInfo *Region, const uptr BlockSize, 1295 const uptr AllocatedUserEnd, const uptr CompactPtrBase) 1296 REQUIRES(Region->MMLock, Region->FLLock) { 1297 const uptr GroupSize = (1UL << GroupSizeLog); 1298 const uptr PageSize = getPageSizeCached(); 1299 SinglyLinkedList<BatchGroupT> GroupsToRelease; 1300 1301 // We are examining each group and will take the minimum distance to the 1302 // release threshold as the next Region::TryReleaseThreshold(). Note that if 1303 // the size of free blocks has reached the release threshold, the distance 1304 // to the next release will be PageSize * SmallerBlockReleasePageDelta. See 1305 // the comment on `SmallerBlockReleasePageDelta` for more details. 1306 uptr MinDistToThreshold = GroupSize; 1307 1308 for (BatchGroupT *BG = Region->FreeListInfo.BlockList.front(), 1309 *Prev = nullptr; 1310 BG != nullptr;) { 1311 // Group boundary is always GroupSize-aligned from CompactPtr base. The 1312 // layout of memory groups is like, 1313 // 1314 // (CompactPtrBase) 1315 // #1 CompactPtrGroupBase #2 CompactPtrGroupBase ... 1316 // | | | 1317 // v v v 1318 // +-----------------------+-----------------------+ 1319 // \ / \ / 1320 // --- GroupSize --- --- GroupSize --- 1321 // 1322 // After decompacting the CompactPtrGroupBase, we expect the alignment 1323 // property is held as well. 1324 const uptr BatchGroupBase = 1325 decompactGroupBase(CompactPtrBase, BG->CompactPtrGroupBase); 1326 DCHECK_LE(Region->RegionBeg, BatchGroupBase); 1327 DCHECK_GE(AllocatedUserEnd, BatchGroupBase); 1328 DCHECK_EQ((Region->RegionBeg - BatchGroupBase) % GroupSize, 0U); 1329 // TransferBatches are pushed in front of BG.Batches. The first one may 1330 // not have all caches used. 1331 const uptr NumBlocks = (BG->Batches.size() - 1) * BG->MaxCachedPerBatch + 1332 BG->Batches.front()->getCount(); 1333 const uptr BytesInBG = NumBlocks * BlockSize; 1334 1335 if (BytesInBG <= BG->BytesInBGAtLastCheckpoint) { 1336 BG->BytesInBGAtLastCheckpoint = BytesInBG; 1337 Prev = BG; 1338 BG = BG->Next; 1339 continue; 1340 } 1341 1342 const uptr PushedBytesDelta = BG->BytesInBGAtLastCheckpoint - BytesInBG; 1343 1344 // Given the randomness property, we try to release the pages only if the 1345 // bytes used by free blocks exceed certain proportion of group size. Note 1346 // that this heuristic only applies when all the spaces in a BatchGroup 1347 // are allocated. 1348 if (isSmallBlock(BlockSize)) { 1349 const uptr BatchGroupEnd = BatchGroupBase + GroupSize; 1350 const uptr AllocatedGroupSize = AllocatedUserEnd >= BatchGroupEnd 1351 ? GroupSize 1352 : AllocatedUserEnd - BatchGroupBase; 1353 const uptr ReleaseThreshold = 1354 (AllocatedGroupSize * (100 - 1U - BlockSize / 16U)) / 100U; 1355 const bool HighDensity = BytesInBG >= ReleaseThreshold; 1356 const bool MayHaveReleasedAll = NumBlocks >= (GroupSize / BlockSize); 1357 // If all blocks in the group are released, we will do range marking 1358 // which is fast. Otherwise, we will wait until we have accumulated 1359 // a certain amount of free memory. 1360 const bool ReachReleaseDelta = 1361 MayHaveReleasedAll 1362 ? true 1363 : PushedBytesDelta >= PageSize * SmallerBlockReleasePageDelta; 1364 1365 if (!HighDensity) { 1366 DCHECK_LE(BytesInBG, ReleaseThreshold); 1367 // The following is the usage of a memroy group, 1368 // 1369 // BytesInBG ReleaseThreshold 1370 // / \ v 1371 // +---+---------------------------+-----+ 1372 // | | | | | 1373 // +---+---------------------------+-----+ 1374 // \ / ^ 1375 // PushedBytesDelta GroupEnd 1376 MinDistToThreshold = 1377 Min(MinDistToThreshold, 1378 ReleaseThreshold - BytesInBG + PushedBytesDelta); 1379 } else { 1380 // If it reaches high density at this round, the next time we will try 1381 // to release is based on SmallerBlockReleasePageDelta 1382 MinDistToThreshold = 1383 Min(MinDistToThreshold, PageSize * SmallerBlockReleasePageDelta); 1384 } 1385 1386 if (!HighDensity || !ReachReleaseDelta) { 1387 Prev = BG; 1388 BG = BG->Next; 1389 continue; 1390 } 1391 } 1392 1393 // If `BG` is the first BatchGroupT in the list, we only need to advance 1394 // `BG` and call FreeListInfo.BlockList::pop_front(). No update is needed 1395 // for `Prev`. 1396 // 1397 // (BG) (BG->Next) 1398 // Prev Cur BG 1399 // | | | 1400 // v v v 1401 // nil +--+ +--+ 1402 // |X | -> | | -> ... 1403 // +--+ +--+ 1404 // 1405 // Otherwise, `Prev` will be used to extract the `Cur` from the 1406 // `FreeListInfo.BlockList`. 1407 // 1408 // (BG) (BG->Next) 1409 // Prev Cur BG 1410 // | | | 1411 // v v v 1412 // +--+ +--+ +--+ 1413 // | | -> |X | -> | | -> ... 1414 // +--+ +--+ +--+ 1415 // 1416 // After FreeListInfo.BlockList::extract(), 1417 // 1418 // Prev Cur BG 1419 // | | | 1420 // v v v 1421 // +--+ +--+ +--+ 1422 // | |-+ |X | +->| | -> ... 1423 // +--+ | +--+ | +--+ 1424 // +--------+ 1425 // 1426 // Note that we need to advance before pushing this BatchGroup to 1427 // GroupsToRelease because it's a destructive operation. 1428 1429 BatchGroupT *Cur = BG; 1430 BG = BG->Next; 1431 1432 // Ideally, we may want to update this only after successful release. 1433 // However, for smaller blocks, each block marking is a costly operation. 1434 // Therefore, we update it earlier. 1435 // TODO: Consider updating this after releasing pages if `ReleaseRecorder` 1436 // can tell the released bytes in each group. 1437 Cur->BytesInBGAtLastCheckpoint = BytesInBG; 1438 1439 if (Prev != nullptr) 1440 Region->FreeListInfo.BlockList.extract(Prev, Cur); 1441 else 1442 Region->FreeListInfo.BlockList.pop_front(); 1443 GroupsToRelease.push_back(Cur); 1444 } 1445 1446 // Only small blocks have the adaptive `TryReleaseThreshold`. 1447 if (isSmallBlock(BlockSize)) { 1448 // If the MinDistToThreshold is not updated, that means each memory group 1449 // may have only pushed less than a page size. In that case, just set it 1450 // back to normal. 1451 if (MinDistToThreshold == GroupSize) 1452 MinDistToThreshold = PageSize * SmallerBlockReleasePageDelta; 1453 Region->TryReleaseThreshold = MinDistToThreshold; 1454 } 1455 1456 return GroupsToRelease; 1457 } 1458 1459 PageReleaseContext 1460 markFreeBlocks(RegionInfo *Region, const uptr BlockSize, 1461 const uptr AllocatedUserEnd, const uptr CompactPtrBase, 1462 SinglyLinkedList<BatchGroupT> &GroupsToRelease) 1463 REQUIRES(Region->MMLock) EXCLUDES(Region->FLLock) { 1464 const uptr GroupSize = (1UL << GroupSizeLog); 1465 auto DecompactPtr = [CompactPtrBase](CompactPtrT CompactPtr) { 1466 return decompactPtrInternal(CompactPtrBase, CompactPtr); 1467 }; 1468 1469 const uptr ReleaseBase = decompactGroupBase( 1470 CompactPtrBase, GroupsToRelease.front()->CompactPtrGroupBase); 1471 const uptr LastGroupEnd = 1472 Min(decompactGroupBase(CompactPtrBase, 1473 GroupsToRelease.back()->CompactPtrGroupBase) + 1474 GroupSize, 1475 AllocatedUserEnd); 1476 // The last block may straddle the group boundary. Rounding up to BlockSize 1477 // to get the exact range. 1478 const uptr ReleaseEnd = 1479 roundUpSlow(LastGroupEnd - Region->RegionBeg, BlockSize) + 1480 Region->RegionBeg; 1481 const uptr ReleaseRangeSize = ReleaseEnd - ReleaseBase; 1482 const uptr ReleaseOffset = ReleaseBase - Region->RegionBeg; 1483 1484 PageReleaseContext Context(BlockSize, /*NumberOfRegions=*/1U, 1485 ReleaseRangeSize, ReleaseOffset); 1486 // We may not be able to do the page release in a rare case that we may 1487 // fail on PageMap allocation. 1488 if (UNLIKELY(!Context.ensurePageMapAllocated())) 1489 return Context; 1490 1491 for (BatchGroupT &BG : GroupsToRelease) { 1492 const uptr BatchGroupBase = 1493 decompactGroupBase(CompactPtrBase, BG.CompactPtrGroupBase); 1494 const uptr BatchGroupEnd = BatchGroupBase + GroupSize; 1495 const uptr AllocatedGroupSize = AllocatedUserEnd >= BatchGroupEnd 1496 ? GroupSize 1497 : AllocatedUserEnd - BatchGroupBase; 1498 const uptr BatchGroupUsedEnd = BatchGroupBase + AllocatedGroupSize; 1499 const bool MayContainLastBlockInRegion = 1500 BatchGroupUsedEnd == AllocatedUserEnd; 1501 const bool BlockAlignedWithUsedEnd = 1502 (BatchGroupUsedEnd - Region->RegionBeg) % BlockSize == 0; 1503 1504 uptr MaxContainedBlocks = AllocatedGroupSize / BlockSize; 1505 if (!BlockAlignedWithUsedEnd) 1506 ++MaxContainedBlocks; 1507 1508 const uptr NumBlocks = (BG.Batches.size() - 1) * BG.MaxCachedPerBatch + 1509 BG.Batches.front()->getCount(); 1510 1511 if (NumBlocks == MaxContainedBlocks) { 1512 for (const auto &It : BG.Batches) { 1513 if (&It != BG.Batches.front()) 1514 DCHECK_EQ(It.getCount(), BG.MaxCachedPerBatch); 1515 for (u16 I = 0; I < It.getCount(); ++I) 1516 DCHECK_EQ(compactPtrGroup(It.get(I)), BG.CompactPtrGroupBase); 1517 } 1518 1519 Context.markRangeAsAllCounted(BatchGroupBase, BatchGroupUsedEnd, 1520 Region->RegionBeg, /*RegionIndex=*/0, 1521 Region->MemMapInfo.AllocatedUser); 1522 } else { 1523 DCHECK_LT(NumBlocks, MaxContainedBlocks); 1524 // Note that we don't always visit blocks in each BatchGroup so that we 1525 // may miss the chance of releasing certain pages that cross 1526 // BatchGroups. 1527 Context.markFreeBlocksInRegion( 1528 BG.Batches, DecompactPtr, Region->RegionBeg, /*RegionIndex=*/0, 1529 Region->MemMapInfo.AllocatedUser, MayContainLastBlockInRegion); 1530 } 1531 } 1532 1533 DCHECK(Context.hasBlockMarked()); 1534 1535 return Context; 1536 } 1537 1538 void mergeGroupsToReleaseBack(RegionInfo *Region, 1539 SinglyLinkedList<BatchGroupT> &GroupsToRelease) 1540 REQUIRES(Region->MMLock) EXCLUDES(Region->FLLock) { 1541 ScopedLock L(Region->FLLock); 1542 1543 // After merging two freelists, we may have redundant `BatchGroup`s that 1544 // need to be recycled. The number of unused `BatchGroup`s is expected to be 1545 // small. Pick a constant which is inferred from real programs. 1546 constexpr uptr MaxUnusedSize = 8; 1547 CompactPtrT Blocks[MaxUnusedSize]; 1548 u32 Idx = 0; 1549 RegionInfo *BatchClassRegion = getRegionInfo(SizeClassMap::BatchClassId); 1550 // We can't call pushBatchClassBlocks() to recycle the unused `BatchGroup`s 1551 // when we are manipulating the freelist of `BatchClassRegion`. Instead, we 1552 // should just push it back to the freelist when we merge two `BatchGroup`s. 1553 // This logic hasn't been implemented because we haven't supported releasing 1554 // pages in `BatchClassRegion`. 1555 DCHECK_NE(BatchClassRegion, Region); 1556 1557 // Merge GroupsToRelease back to the Region::FreeListInfo.BlockList. Note 1558 // that both `Region->FreeListInfo.BlockList` and `GroupsToRelease` are 1559 // sorted. 1560 for (BatchGroupT *BG = Region->FreeListInfo.BlockList.front(), 1561 *Prev = nullptr; 1562 ;) { 1563 if (BG == nullptr || GroupsToRelease.empty()) { 1564 if (!GroupsToRelease.empty()) 1565 Region->FreeListInfo.BlockList.append_back(&GroupsToRelease); 1566 break; 1567 } 1568 1569 DCHECK(!BG->Batches.empty()); 1570 1571 if (BG->CompactPtrGroupBase < 1572 GroupsToRelease.front()->CompactPtrGroupBase) { 1573 Prev = BG; 1574 BG = BG->Next; 1575 continue; 1576 } 1577 1578 BatchGroupT *Cur = GroupsToRelease.front(); 1579 TransferBatchT *UnusedTransferBatch = nullptr; 1580 GroupsToRelease.pop_front(); 1581 1582 if (BG->CompactPtrGroupBase == Cur->CompactPtrGroupBase) { 1583 BG->PushedBlocks += Cur->PushedBlocks; 1584 // We have updated `BatchGroup::BytesInBGAtLastCheckpoint` while 1585 // collecting the `GroupsToRelease`. 1586 BG->BytesInBGAtLastCheckpoint = Cur->BytesInBGAtLastCheckpoint; 1587 const uptr MaxCachedPerBatch = BG->MaxCachedPerBatch; 1588 1589 // Note that the first TransferBatches in both `Batches` may not be 1590 // full and only the first TransferBatch can have non-full blocks. Thus 1591 // we have to merge them before appending one to another. 1592 if (Cur->Batches.front()->getCount() == MaxCachedPerBatch) { 1593 BG->Batches.append_back(&Cur->Batches); 1594 } else { 1595 TransferBatchT *NonFullBatch = Cur->Batches.front(); 1596 Cur->Batches.pop_front(); 1597 const u16 NonFullBatchCount = NonFullBatch->getCount(); 1598 // The remaining Batches in `Cur` are full. 1599 BG->Batches.append_back(&Cur->Batches); 1600 1601 if (BG->Batches.front()->getCount() == MaxCachedPerBatch) { 1602 // Only 1 non-full TransferBatch, push it to the front. 1603 BG->Batches.push_front(NonFullBatch); 1604 } else { 1605 const u16 NumBlocksToMove = static_cast<u16>( 1606 Min(static_cast<u16>(MaxCachedPerBatch - 1607 BG->Batches.front()->getCount()), 1608 NonFullBatchCount)); 1609 BG->Batches.front()->appendFromTransferBatch(NonFullBatch, 1610 NumBlocksToMove); 1611 if (NonFullBatch->isEmpty()) 1612 UnusedTransferBatch = NonFullBatch; 1613 else 1614 BG->Batches.push_front(NonFullBatch); 1615 } 1616 } 1617 1618 const u32 NeededSlots = UnusedTransferBatch == nullptr ? 1U : 2U; 1619 if (UNLIKELY(Idx + NeededSlots > MaxUnusedSize)) { 1620 ScopedLock L(BatchClassRegion->FLLock); 1621 pushBatchClassBlocks(BatchClassRegion, Blocks, Idx); 1622 if (conditionVariableEnabled()) 1623 BatchClassRegion->FLLockCV.notifyAll(BatchClassRegion->FLLock); 1624 Idx = 0; 1625 } 1626 Blocks[Idx++] = 1627 compactPtr(SizeClassMap::BatchClassId, reinterpret_cast<uptr>(Cur)); 1628 if (UnusedTransferBatch) { 1629 Blocks[Idx++] = 1630 compactPtr(SizeClassMap::BatchClassId, 1631 reinterpret_cast<uptr>(UnusedTransferBatch)); 1632 } 1633 Prev = BG; 1634 BG = BG->Next; 1635 continue; 1636 } 1637 1638 // At here, the `BG` is the first BatchGroup with CompactPtrGroupBase 1639 // larger than the first element in `GroupsToRelease`. We need to insert 1640 // `GroupsToRelease::front()` (which is `Cur` below) before `BG`. 1641 // 1642 // 1. If `Prev` is nullptr, we simply push `Cur` to the front of 1643 // FreeListInfo.BlockList. 1644 // 2. Otherwise, use `insert()` which inserts an element next to `Prev`. 1645 // 1646 // Afterwards, we don't need to advance `BG` because the order between 1647 // `BG` and the new `GroupsToRelease::front()` hasn't been checked. 1648 if (Prev == nullptr) 1649 Region->FreeListInfo.BlockList.push_front(Cur); 1650 else 1651 Region->FreeListInfo.BlockList.insert(Prev, Cur); 1652 DCHECK_EQ(Cur->Next, BG); 1653 Prev = Cur; 1654 } 1655 1656 if (Idx != 0) { 1657 ScopedLock L(BatchClassRegion->FLLock); 1658 pushBatchClassBlocks(BatchClassRegion, Blocks, Idx); 1659 if (conditionVariableEnabled()) 1660 BatchClassRegion->FLLockCV.notifyAll(BatchClassRegion->FLLock); 1661 } 1662 1663 if (SCUDO_DEBUG) { 1664 BatchGroupT *Prev = Region->FreeListInfo.BlockList.front(); 1665 for (BatchGroupT *Cur = Prev->Next; Cur != nullptr; 1666 Prev = Cur, Cur = Cur->Next) { 1667 CHECK_LT(Prev->CompactPtrGroupBase, Cur->CompactPtrGroupBase); 1668 } 1669 } 1670 1671 if (conditionVariableEnabled()) 1672 Region->FLLockCV.notifyAll(Region->FLLock); 1673 } 1674 1675 // TODO: `PrimaryBase` can be obtained from ReservedMemory. This needs to be 1676 // deprecated. 1677 uptr PrimaryBase = 0; 1678 ReservedMemoryT ReservedMemory = {}; 1679 // The minimum size of pushed blocks that we will try to release the pages in 1680 // that size class. 1681 uptr SmallerBlockReleasePageDelta = 0; 1682 atomic_s32 ReleaseToOsIntervalMs = {}; 1683 alignas(SCUDO_CACHE_LINE_SIZE) RegionInfo RegionInfoArray[NumClasses]; 1684 }; 1685 1686 } // namespace scudo 1687 1688 #endif // SCUDO_PRIMARY64_H_ 1689