1 //===-- primary32.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_PRIMARY32_H_ 10 #define SCUDO_PRIMARY32_H_ 11 12 #include "bytemap.h" 13 #include "common.h" 14 #include "list.h" 15 #include "local_cache.h" 16 #include "options.h" 17 #include "release.h" 18 #include "report.h" 19 #include "stats.h" 20 #include "string_utils.h" 21 #include "thread_annotations.h" 22 23 namespace scudo { 24 25 // SizeClassAllocator32 is an allocator for 32 or 64-bit address space. 26 // 27 // It maps Regions of 2^RegionSizeLog bytes aligned on a 2^RegionSizeLog bytes 28 // boundary, and keeps a bytemap of the mappable address space to track the size 29 // class they are associated with. 30 // 31 // Mapped regions are split into equally sized Blocks according to the size 32 // class they belong to, and the associated pointers are shuffled to prevent any 33 // predictable address pattern (the predictability increases with the block 34 // size). 35 // 36 // Regions for size class 0 are special and used to hold TransferBatches, which 37 // allow to transfer arrays of pointers from the global size class freelist to 38 // the thread specific freelist for said class, and back. 39 // 40 // Memory used by this allocator is never unmapped but can be partially 41 // reclaimed if the platform allows for it. 42 43 template <typename Config> class SizeClassAllocator32 { 44 public: 45 typedef typename Config::Primary::CompactPtrT CompactPtrT; 46 typedef typename Config::Primary::SizeClassMap SizeClassMap; 47 static const uptr GroupSizeLog = Config::Primary::GroupSizeLog; 48 // The bytemap can only track UINT8_MAX - 1 classes. 49 static_assert(SizeClassMap::LargestClassId <= (UINT8_MAX - 1), ""); 50 // Regions should be large enough to hold the largest Block. 51 static_assert((1UL << Config::Primary::RegionSizeLog) >= 52 SizeClassMap::MaxSize, 53 ""); 54 typedef SizeClassAllocator32<Config> ThisT; 55 typedef SizeClassAllocatorLocalCache<ThisT> CacheT; 56 typedef typename CacheT::TransferBatch TransferBatch; 57 typedef typename CacheT::BatchGroup BatchGroup; 58 59 static uptr getSizeByClassId(uptr ClassId) { 60 return (ClassId == SizeClassMap::BatchClassId) 61 ? sizeof(TransferBatch) 62 : SizeClassMap::getSizeByClassId(ClassId); 63 } 64 65 static bool canAllocate(uptr Size) { return Size <= SizeClassMap::MaxSize; } 66 67 void init(s32 ReleaseToOsInterval) NO_THREAD_SAFETY_ANALYSIS { 68 if (SCUDO_FUCHSIA) 69 reportError("SizeClassAllocator32 is not supported on Fuchsia"); 70 71 if (SCUDO_TRUSTY) 72 reportError("SizeClassAllocator32 is not supported on Trusty"); 73 74 DCHECK(isAligned(reinterpret_cast<uptr>(this), alignof(ThisT))); 75 PossibleRegions.init(); 76 u32 Seed; 77 const u64 Time = getMonotonicTimeFast(); 78 if (!getRandom(reinterpret_cast<void *>(&Seed), sizeof(Seed))) 79 Seed = static_cast<u32>( 80 Time ^ (reinterpret_cast<uptr>(SizeClassInfoArray) >> 6)); 81 for (uptr I = 0; I < NumClasses; I++) { 82 SizeClassInfo *Sci = getSizeClassInfo(I); 83 Sci->RandState = getRandomU32(&Seed); 84 // Sci->MaxRegionIndex is already initialized to 0. 85 Sci->MinRegionIndex = NumRegions; 86 Sci->ReleaseInfo.LastReleaseAtNs = Time; 87 } 88 setOption(Option::ReleaseInterval, static_cast<sptr>(ReleaseToOsInterval)); 89 } 90 91 void unmapTestOnly() { 92 { 93 ScopedLock L(RegionsStashMutex); 94 while (NumberOfStashedRegions > 0) { 95 unmap(reinterpret_cast<void *>(RegionsStash[--NumberOfStashedRegions]), 96 RegionSize); 97 } 98 } 99 100 uptr MinRegionIndex = NumRegions, MaxRegionIndex = 0; 101 for (uptr I = 0; I < NumClasses; I++) { 102 SizeClassInfo *Sci = getSizeClassInfo(I); 103 ScopedLock L(Sci->Mutex); 104 if (Sci->MinRegionIndex < MinRegionIndex) 105 MinRegionIndex = Sci->MinRegionIndex; 106 if (Sci->MaxRegionIndex > MaxRegionIndex) 107 MaxRegionIndex = Sci->MaxRegionIndex; 108 *Sci = {}; 109 } 110 111 ScopedLock L(ByteMapMutex); 112 for (uptr I = MinRegionIndex; I <= MaxRegionIndex; I++) 113 if (PossibleRegions[I]) 114 unmap(reinterpret_cast<void *>(I * RegionSize), RegionSize); 115 PossibleRegions.unmapTestOnly(); 116 } 117 118 // When all blocks are freed, it has to be the same size as `AllocatedUser`. 119 void verifyAllBlocksAreReleasedTestOnly() { 120 // `BatchGroup` and `TransferBatch` also use the blocks from BatchClass. 121 uptr BatchClassUsedInFreeLists = 0; 122 for (uptr I = 0; I < NumClasses; I++) { 123 // We have to count BatchClassUsedInFreeLists in other regions first. 124 if (I == SizeClassMap::BatchClassId) 125 continue; 126 SizeClassInfo *Sci = getSizeClassInfo(I); 127 ScopedLock L1(Sci->Mutex); 128 uptr TotalBlocks = 0; 129 for (BatchGroup &BG : Sci->FreeListInfo.BlockList) { 130 // `BG::Batches` are `TransferBatches`. +1 for `BatchGroup`. 131 BatchClassUsedInFreeLists += BG.Batches.size() + 1; 132 for (const auto &It : BG.Batches) 133 TotalBlocks += It.getCount(); 134 } 135 136 const uptr BlockSize = getSizeByClassId(I); 137 DCHECK_EQ(TotalBlocks, Sci->AllocatedUser / BlockSize); 138 DCHECK_EQ(Sci->FreeListInfo.PushedBlocks, Sci->FreeListInfo.PoppedBlocks); 139 } 140 141 SizeClassInfo *Sci = getSizeClassInfo(SizeClassMap::BatchClassId); 142 ScopedLock L1(Sci->Mutex); 143 uptr TotalBlocks = 0; 144 for (BatchGroup &BG : Sci->FreeListInfo.BlockList) { 145 if (LIKELY(!BG.Batches.empty())) { 146 for (const auto &It : BG.Batches) 147 TotalBlocks += It.getCount(); 148 } else { 149 // `BatchGroup` with empty freelist doesn't have `TransferBatch` record 150 // itself. 151 ++TotalBlocks; 152 } 153 } 154 155 const uptr BlockSize = getSizeByClassId(SizeClassMap::BatchClassId); 156 DCHECK_EQ(TotalBlocks + BatchClassUsedInFreeLists, 157 Sci->AllocatedUser / BlockSize); 158 const uptr BlocksInUse = 159 Sci->FreeListInfo.PoppedBlocks - Sci->FreeListInfo.PushedBlocks; 160 DCHECK_EQ(BlocksInUse, BatchClassUsedInFreeLists); 161 } 162 163 CompactPtrT compactPtr(UNUSED uptr ClassId, uptr Ptr) const { 164 return static_cast<CompactPtrT>(Ptr); 165 } 166 167 void *decompactPtr(UNUSED uptr ClassId, CompactPtrT CompactPtr) const { 168 return reinterpret_cast<void *>(static_cast<uptr>(CompactPtr)); 169 } 170 171 uptr compactPtrGroupBase(CompactPtrT CompactPtr) { 172 const uptr Mask = (static_cast<uptr>(1) << GroupSizeLog) - 1; 173 return CompactPtr & ~Mask; 174 } 175 176 uptr decompactGroupBase(uptr CompactPtrGroupBase) { 177 return CompactPtrGroupBase; 178 } 179 180 ALWAYS_INLINE static bool isSmallBlock(uptr BlockSize) { 181 const uptr PageSize = getPageSizeCached(); 182 return BlockSize < PageSize / 16U; 183 } 184 185 ALWAYS_INLINE static bool isLargeBlock(uptr BlockSize) { 186 const uptr PageSize = getPageSizeCached(); 187 return BlockSize > PageSize; 188 } 189 190 TransferBatch *popBatch(CacheT *C, uptr ClassId) { 191 DCHECK_LT(ClassId, NumClasses); 192 SizeClassInfo *Sci = getSizeClassInfo(ClassId); 193 ScopedLock L(Sci->Mutex); 194 TransferBatch *B = popBatchImpl(C, ClassId, Sci); 195 if (UNLIKELY(!B)) { 196 if (UNLIKELY(!populateFreeList(C, ClassId, Sci))) 197 return nullptr; 198 B = popBatchImpl(C, ClassId, Sci); 199 // if `populateFreeList` succeeded, we are supposed to get free blocks. 200 DCHECK_NE(B, nullptr); 201 } 202 return B; 203 } 204 205 // Push the array of free blocks to the designated batch group. 206 void pushBlocks(CacheT *C, uptr ClassId, CompactPtrT *Array, u32 Size) { 207 DCHECK_LT(ClassId, NumClasses); 208 DCHECK_GT(Size, 0); 209 210 SizeClassInfo *Sci = getSizeClassInfo(ClassId); 211 if (ClassId == SizeClassMap::BatchClassId) { 212 ScopedLock L(Sci->Mutex); 213 pushBatchClassBlocks(Sci, Array, Size); 214 return; 215 } 216 217 // TODO(chiahungduan): Consider not doing grouping if the group size is not 218 // greater than the block size with a certain scale. 219 220 // Sort the blocks so that blocks belonging to the same group can be pushed 221 // together. 222 bool SameGroup = true; 223 for (u32 I = 1; I < Size; ++I) { 224 if (compactPtrGroupBase(Array[I - 1]) != compactPtrGroupBase(Array[I])) 225 SameGroup = false; 226 CompactPtrT Cur = Array[I]; 227 u32 J = I; 228 while (J > 0 && 229 compactPtrGroupBase(Cur) < compactPtrGroupBase(Array[J - 1])) { 230 Array[J] = Array[J - 1]; 231 --J; 232 } 233 Array[J] = Cur; 234 } 235 236 ScopedLock L(Sci->Mutex); 237 pushBlocksImpl(C, ClassId, Sci, Array, Size, SameGroup); 238 } 239 240 void disable() NO_THREAD_SAFETY_ANALYSIS { 241 // The BatchClassId must be locked last since other classes can use it. 242 for (sptr I = static_cast<sptr>(NumClasses) - 1; I >= 0; I--) { 243 if (static_cast<uptr>(I) == SizeClassMap::BatchClassId) 244 continue; 245 getSizeClassInfo(static_cast<uptr>(I))->Mutex.lock(); 246 } 247 getSizeClassInfo(SizeClassMap::BatchClassId)->Mutex.lock(); 248 RegionsStashMutex.lock(); 249 ByteMapMutex.lock(); 250 } 251 252 void enable() NO_THREAD_SAFETY_ANALYSIS { 253 ByteMapMutex.unlock(); 254 RegionsStashMutex.unlock(); 255 getSizeClassInfo(SizeClassMap::BatchClassId)->Mutex.unlock(); 256 for (uptr I = 0; I < NumClasses; I++) { 257 if (I == SizeClassMap::BatchClassId) 258 continue; 259 getSizeClassInfo(I)->Mutex.unlock(); 260 } 261 } 262 263 template <typename F> void iterateOverBlocks(F Callback) { 264 uptr MinRegionIndex = NumRegions, MaxRegionIndex = 0; 265 for (uptr I = 0; I < NumClasses; I++) { 266 SizeClassInfo *Sci = getSizeClassInfo(I); 267 // TODO: The call of `iterateOverBlocks` requires disabling 268 // SizeClassAllocator32. We may consider locking each region on demand 269 // only. 270 Sci->Mutex.assertHeld(); 271 if (Sci->MinRegionIndex < MinRegionIndex) 272 MinRegionIndex = Sci->MinRegionIndex; 273 if (Sci->MaxRegionIndex > MaxRegionIndex) 274 MaxRegionIndex = Sci->MaxRegionIndex; 275 } 276 277 // SizeClassAllocator32 is disabled, i.e., ByteMapMutex is held. 278 ByteMapMutex.assertHeld(); 279 280 for (uptr I = MinRegionIndex; I <= MaxRegionIndex; I++) { 281 if (PossibleRegions[I] && 282 (PossibleRegions[I] - 1U) != SizeClassMap::BatchClassId) { 283 const uptr BlockSize = getSizeByClassId(PossibleRegions[I] - 1U); 284 const uptr From = I * RegionSize; 285 const uptr To = From + (RegionSize / BlockSize) * BlockSize; 286 for (uptr Block = From; Block < To; Block += BlockSize) 287 Callback(Block); 288 } 289 } 290 } 291 292 void getStats(ScopedString *Str) { 293 // TODO(kostyak): get the RSS per region. 294 uptr TotalMapped = 0; 295 uptr PoppedBlocks = 0; 296 uptr PushedBlocks = 0; 297 for (uptr I = 0; I < NumClasses; I++) { 298 SizeClassInfo *Sci = getSizeClassInfo(I); 299 ScopedLock L(Sci->Mutex); 300 TotalMapped += Sci->AllocatedUser; 301 PoppedBlocks += Sci->FreeListInfo.PoppedBlocks; 302 PushedBlocks += Sci->FreeListInfo.PushedBlocks; 303 } 304 Str->append("Stats: SizeClassAllocator32: %zuM mapped in %zu allocations; " 305 "remains %zu\n", 306 TotalMapped >> 20, PoppedBlocks, PoppedBlocks - PushedBlocks); 307 for (uptr I = 0; I < NumClasses; I++) { 308 SizeClassInfo *Sci = getSizeClassInfo(I); 309 ScopedLock L(Sci->Mutex); 310 getStats(Str, I, Sci); 311 } 312 } 313 314 bool setOption(Option O, sptr Value) { 315 if (O == Option::ReleaseInterval) { 316 const s32 Interval = Max(Min(static_cast<s32>(Value), 317 Config::Primary::MaxReleaseToOsIntervalMs), 318 Config::Primary::MinReleaseToOsIntervalMs); 319 atomic_store_relaxed(&ReleaseToOsIntervalMs, Interval); 320 return true; 321 } 322 // Not supported by the Primary, but not an error either. 323 return true; 324 } 325 326 uptr tryReleaseToOS(uptr ClassId, ReleaseToOS ReleaseType) { 327 SizeClassInfo *Sci = getSizeClassInfo(ClassId); 328 // TODO: Once we have separate locks like primary64, we may consider using 329 // tryLock() as well. 330 ScopedLock L(Sci->Mutex); 331 return releaseToOSMaybe(Sci, ClassId, ReleaseType); 332 } 333 334 uptr releaseToOS(ReleaseToOS ReleaseType) { 335 uptr TotalReleasedBytes = 0; 336 for (uptr I = 0; I < NumClasses; I++) { 337 if (I == SizeClassMap::BatchClassId) 338 continue; 339 SizeClassInfo *Sci = getSizeClassInfo(I); 340 ScopedLock L(Sci->Mutex); 341 TotalReleasedBytes += releaseToOSMaybe(Sci, I, ReleaseType); 342 } 343 return TotalReleasedBytes; 344 } 345 346 const char *getRegionInfoArrayAddress() const { return nullptr; } 347 static uptr getRegionInfoArraySize() { return 0; } 348 349 static BlockInfo findNearestBlock(UNUSED const char *RegionInfoData, 350 UNUSED uptr Ptr) { 351 return {}; 352 } 353 354 AtomicOptions Options; 355 356 private: 357 static const uptr NumClasses = SizeClassMap::NumClasses; 358 static const uptr RegionSize = 1UL << Config::Primary::RegionSizeLog; 359 static const uptr NumRegions = 360 SCUDO_MMAP_RANGE_SIZE >> Config::Primary::RegionSizeLog; 361 static const u32 MaxNumBatches = SCUDO_ANDROID ? 4U : 8U; 362 typedef FlatByteMap<NumRegions> ByteMap; 363 364 struct ReleaseToOsInfo { 365 uptr BytesInFreeListAtLastCheckpoint; 366 uptr RangesReleased; 367 uptr LastReleasedBytes; 368 u64 LastReleaseAtNs; 369 }; 370 371 struct BlocksInfo { 372 SinglyLinkedList<BatchGroup> BlockList = {}; 373 uptr PoppedBlocks = 0; 374 uptr PushedBlocks = 0; 375 }; 376 377 struct alignas(SCUDO_CACHE_LINE_SIZE) SizeClassInfo { 378 HybridMutex Mutex; 379 BlocksInfo FreeListInfo GUARDED_BY(Mutex); 380 uptr CurrentRegion GUARDED_BY(Mutex); 381 uptr CurrentRegionAllocated GUARDED_BY(Mutex); 382 u32 RandState; 383 uptr AllocatedUser GUARDED_BY(Mutex); 384 // Lowest & highest region index allocated for this size class, to avoid 385 // looping through the whole NumRegions. 386 uptr MinRegionIndex GUARDED_BY(Mutex); 387 uptr MaxRegionIndex GUARDED_BY(Mutex); 388 ReleaseToOsInfo ReleaseInfo GUARDED_BY(Mutex); 389 }; 390 static_assert(sizeof(SizeClassInfo) % SCUDO_CACHE_LINE_SIZE == 0, ""); 391 392 uptr computeRegionId(uptr Mem) { 393 const uptr Id = Mem >> Config::Primary::RegionSizeLog; 394 CHECK_LT(Id, NumRegions); 395 return Id; 396 } 397 398 uptr allocateRegionSlow() { 399 uptr MapSize = 2 * RegionSize; 400 const uptr MapBase = reinterpret_cast<uptr>( 401 map(nullptr, MapSize, "scudo:primary", MAP_ALLOWNOMEM)); 402 if (!MapBase) 403 return 0; 404 const uptr MapEnd = MapBase + MapSize; 405 uptr Region = MapBase; 406 if (isAligned(Region, RegionSize)) { 407 ScopedLock L(RegionsStashMutex); 408 if (NumberOfStashedRegions < MaxStashedRegions) 409 RegionsStash[NumberOfStashedRegions++] = MapBase + RegionSize; 410 else 411 MapSize = RegionSize; 412 } else { 413 Region = roundUp(MapBase, RegionSize); 414 unmap(reinterpret_cast<void *>(MapBase), Region - MapBase); 415 MapSize = RegionSize; 416 } 417 const uptr End = Region + MapSize; 418 if (End != MapEnd) 419 unmap(reinterpret_cast<void *>(End), MapEnd - End); 420 421 DCHECK_EQ(Region % RegionSize, 0U); 422 static_assert(Config::Primary::RegionSizeLog == GroupSizeLog, 423 "Memory group should be the same size as Region"); 424 425 return Region; 426 } 427 428 uptr allocateRegion(SizeClassInfo *Sci, uptr ClassId) REQUIRES(Sci->Mutex) { 429 DCHECK_LT(ClassId, NumClasses); 430 uptr Region = 0; 431 { 432 ScopedLock L(RegionsStashMutex); 433 if (NumberOfStashedRegions > 0) 434 Region = RegionsStash[--NumberOfStashedRegions]; 435 } 436 if (!Region) 437 Region = allocateRegionSlow(); 438 if (LIKELY(Region)) { 439 // Sci->Mutex is held by the caller, updating the Min/Max is safe. 440 const uptr RegionIndex = computeRegionId(Region); 441 if (RegionIndex < Sci->MinRegionIndex) 442 Sci->MinRegionIndex = RegionIndex; 443 if (RegionIndex > Sci->MaxRegionIndex) 444 Sci->MaxRegionIndex = RegionIndex; 445 ScopedLock L(ByteMapMutex); 446 PossibleRegions.set(RegionIndex, static_cast<u8>(ClassId + 1U)); 447 } 448 return Region; 449 } 450 451 SizeClassInfo *getSizeClassInfo(uptr ClassId) { 452 DCHECK_LT(ClassId, NumClasses); 453 return &SizeClassInfoArray[ClassId]; 454 } 455 456 void pushBatchClassBlocks(SizeClassInfo *Sci, CompactPtrT *Array, u32 Size) 457 REQUIRES(Sci->Mutex) { 458 DCHECK_EQ(Sci, getSizeClassInfo(SizeClassMap::BatchClassId)); 459 460 // Free blocks are recorded by TransferBatch in freelist for all 461 // size-classes. In addition, TransferBatch is allocated from BatchClassId. 462 // In order not to use additional block to record the free blocks in 463 // BatchClassId, they are self-contained. I.e., A TransferBatch records the 464 // block address of itself. See the figure below: 465 // 466 // TransferBatch at 0xABCD 467 // +----------------------------+ 468 // | Free blocks' addr | 469 // | +------+------+------+ | 470 // | |0xABCD|... |... | | 471 // | +------+------+------+ | 472 // +----------------------------+ 473 // 474 // When we allocate all the free blocks in the TransferBatch, the block used 475 // by TransferBatch is also free for use. We don't need to recycle the 476 // TransferBatch. Note that the correctness is maintained by the invariant, 477 // 478 // The unit of each popBatch() request is entire TransferBatch. Return 479 // part of the blocks in a TransferBatch is invalid. 480 // 481 // This ensures that TransferBatch won't leak the address itself while it's 482 // still holding other valid data. 483 // 484 // Besides, BatchGroup is also allocated from BatchClassId and has its 485 // address recorded in the TransferBatch too. To maintain the correctness, 486 // 487 // The address of BatchGroup is always recorded in the last TransferBatch 488 // in the freelist (also imply that the freelist should only be 489 // updated with push_front). Once the last TransferBatch is popped, 490 // the block used by BatchGroup is also free for use. 491 // 492 // With this approach, the blocks used by BatchGroup and TransferBatch are 493 // reusable and don't need additional space for them. 494 495 Sci->FreeListInfo.PushedBlocks += Size; 496 BatchGroup *BG = Sci->FreeListInfo.BlockList.front(); 497 498 if (BG == nullptr) { 499 // Construct `BatchGroup` on the last element. 500 BG = reinterpret_cast<BatchGroup *>( 501 decompactPtr(SizeClassMap::BatchClassId, Array[Size - 1])); 502 --Size; 503 BG->Batches.clear(); 504 // BatchClass hasn't enabled memory group. Use `0` to indicate there's no 505 // memory group here. 506 BG->CompactPtrGroupBase = 0; 507 // `BG` is also the block of BatchClassId. Note that this is different 508 // from `CreateGroup` in `pushBlocksImpl` 509 BG->PushedBlocks = 1; 510 BG->BytesInBGAtLastCheckpoint = 0; 511 BG->MaxCachedPerBatch = TransferBatch::getMaxCached( 512 getSizeByClassId(SizeClassMap::BatchClassId)); 513 514 Sci->FreeListInfo.BlockList.push_front(BG); 515 } 516 517 if (UNLIKELY(Size == 0)) 518 return; 519 520 // This happens under 2 cases. 521 // 1. just allocated a new `BatchGroup`. 522 // 2. Only 1 block is pushed when the freelist is empty. 523 if (BG->Batches.empty()) { 524 // Construct the `TransferBatch` on the last element. 525 TransferBatch *TB = reinterpret_cast<TransferBatch *>( 526 decompactPtr(SizeClassMap::BatchClassId, Array[Size - 1])); 527 TB->clear(); 528 // As mentioned above, addresses of `TransferBatch` and `BatchGroup` are 529 // recorded in the TransferBatch. 530 TB->add(Array[Size - 1]); 531 TB->add( 532 compactPtr(SizeClassMap::BatchClassId, reinterpret_cast<uptr>(BG))); 533 --Size; 534 DCHECK_EQ(BG->PushedBlocks, 1U); 535 // `TB` is also the block of BatchClassId. 536 BG->PushedBlocks += 1; 537 BG->Batches.push_front(TB); 538 } 539 540 TransferBatch *CurBatch = BG->Batches.front(); 541 DCHECK_NE(CurBatch, nullptr); 542 543 for (u32 I = 0; I < Size;) { 544 u16 UnusedSlots = 545 static_cast<u16>(BG->MaxCachedPerBatch - CurBatch->getCount()); 546 if (UnusedSlots == 0) { 547 CurBatch = reinterpret_cast<TransferBatch *>( 548 decompactPtr(SizeClassMap::BatchClassId, Array[I])); 549 CurBatch->clear(); 550 // Self-contained 551 CurBatch->add(Array[I]); 552 ++I; 553 // TODO(chiahungduan): Avoid the use of push_back() in `Batches` of 554 // BatchClassId. 555 BG->Batches.push_front(CurBatch); 556 UnusedSlots = static_cast<u16>(BG->MaxCachedPerBatch - 1); 557 } 558 // `UnusedSlots` is u16 so the result will be also fit in u16. 559 const u16 AppendSize = static_cast<u16>(Min<u32>(UnusedSlots, Size - I)); 560 CurBatch->appendFromArray(&Array[I], AppendSize); 561 I += AppendSize; 562 } 563 564 BG->PushedBlocks += Size; 565 } 566 // Push the blocks to their batch group. The layout will be like, 567 // 568 // FreeListInfo.BlockList - > BG -> BG -> BG 569 // | | | 570 // v v v 571 // TB TB TB 572 // | 573 // v 574 // TB 575 // 576 // Each BlockGroup(BG) will associate with unique group id and the free blocks 577 // are managed by a list of TransferBatch(TB). To reduce the time of inserting 578 // blocks, BGs are sorted and the input `Array` are supposed to be sorted so 579 // that we can get better performance of maintaining sorted property. 580 // Use `SameGroup=true` to indicate that all blocks in the array are from the 581 // same group then we will skip checking the group id of each block. 582 // 583 // The region mutex needs to be held while calling this method. 584 void pushBlocksImpl(CacheT *C, uptr ClassId, SizeClassInfo *Sci, 585 CompactPtrT *Array, u32 Size, bool SameGroup = false) 586 REQUIRES(Sci->Mutex) { 587 DCHECK_NE(ClassId, SizeClassMap::BatchClassId); 588 DCHECK_GT(Size, 0U); 589 590 auto CreateGroup = [&](uptr CompactPtrGroupBase) { 591 BatchGroup *BG = C->createGroup(); 592 BG->Batches.clear(); 593 TransferBatch *TB = C->createBatch(ClassId, nullptr); 594 TB->clear(); 595 596 BG->CompactPtrGroupBase = CompactPtrGroupBase; 597 BG->Batches.push_front(TB); 598 BG->PushedBlocks = 0; 599 BG->BytesInBGAtLastCheckpoint = 0; 600 BG->MaxCachedPerBatch = 601 TransferBatch::getMaxCached(getSizeByClassId(ClassId)); 602 603 return BG; 604 }; 605 606 auto InsertBlocks = [&](BatchGroup *BG, CompactPtrT *Array, u32 Size) { 607 SinglyLinkedList<TransferBatch> &Batches = BG->Batches; 608 TransferBatch *CurBatch = Batches.front(); 609 DCHECK_NE(CurBatch, nullptr); 610 611 for (u32 I = 0; I < Size;) { 612 DCHECK_GE(BG->MaxCachedPerBatch, CurBatch->getCount()); 613 u16 UnusedSlots = 614 static_cast<u16>(BG->MaxCachedPerBatch - CurBatch->getCount()); 615 if (UnusedSlots == 0) { 616 CurBatch = C->createBatch( 617 ClassId, 618 reinterpret_cast<void *>(decompactPtr(ClassId, Array[I]))); 619 CurBatch->clear(); 620 Batches.push_front(CurBatch); 621 UnusedSlots = BG->MaxCachedPerBatch; 622 } 623 // `UnusedSlots` is u16 so the result will be also fit in u16. 624 u16 AppendSize = static_cast<u16>(Min<u32>(UnusedSlots, Size - I)); 625 CurBatch->appendFromArray(&Array[I], AppendSize); 626 I += AppendSize; 627 } 628 629 BG->PushedBlocks += Size; 630 }; 631 632 Sci->FreeListInfo.PushedBlocks += Size; 633 BatchGroup *Cur = Sci->FreeListInfo.BlockList.front(); 634 635 // In the following, `Cur` always points to the BatchGroup for blocks that 636 // will be pushed next. `Prev` is the element right before `Cur`. 637 BatchGroup *Prev = nullptr; 638 639 while (Cur != nullptr && 640 compactPtrGroupBase(Array[0]) > Cur->CompactPtrGroupBase) { 641 Prev = Cur; 642 Cur = Cur->Next; 643 } 644 645 if (Cur == nullptr || 646 compactPtrGroupBase(Array[0]) != Cur->CompactPtrGroupBase) { 647 Cur = CreateGroup(compactPtrGroupBase(Array[0])); 648 if (Prev == nullptr) 649 Sci->FreeListInfo.BlockList.push_front(Cur); 650 else 651 Sci->FreeListInfo.BlockList.insert(Prev, Cur); 652 } 653 654 // All the blocks are from the same group, just push without checking group 655 // id. 656 if (SameGroup) { 657 for (u32 I = 0; I < Size; ++I) 658 DCHECK_EQ(compactPtrGroupBase(Array[I]), Cur->CompactPtrGroupBase); 659 660 InsertBlocks(Cur, Array, Size); 661 return; 662 } 663 664 // The blocks are sorted by group id. Determine the segment of group and 665 // push them to their group together. 666 u32 Count = 1; 667 for (u32 I = 1; I < Size; ++I) { 668 if (compactPtrGroupBase(Array[I - 1]) != compactPtrGroupBase(Array[I])) { 669 DCHECK_EQ(compactPtrGroupBase(Array[I - 1]), Cur->CompactPtrGroupBase); 670 InsertBlocks(Cur, Array + I - Count, Count); 671 672 while (Cur != nullptr && 673 compactPtrGroupBase(Array[I]) > Cur->CompactPtrGroupBase) { 674 Prev = Cur; 675 Cur = Cur->Next; 676 } 677 678 if (Cur == nullptr || 679 compactPtrGroupBase(Array[I]) != Cur->CompactPtrGroupBase) { 680 Cur = CreateGroup(compactPtrGroupBase(Array[I])); 681 DCHECK_NE(Prev, nullptr); 682 Sci->FreeListInfo.BlockList.insert(Prev, Cur); 683 } 684 685 Count = 1; 686 } else { 687 ++Count; 688 } 689 } 690 691 InsertBlocks(Cur, Array + Size - Count, Count); 692 } 693 694 // Pop one TransferBatch from a BatchGroup. The BatchGroup with the smallest 695 // group id will be considered first. 696 // 697 // The region mutex needs to be held while calling this method. 698 TransferBatch *popBatchImpl(CacheT *C, uptr ClassId, SizeClassInfo *Sci) 699 REQUIRES(Sci->Mutex) { 700 if (Sci->FreeListInfo.BlockList.empty()) 701 return nullptr; 702 703 SinglyLinkedList<TransferBatch> &Batches = 704 Sci->FreeListInfo.BlockList.front()->Batches; 705 706 if (Batches.empty()) { 707 DCHECK_EQ(ClassId, SizeClassMap::BatchClassId); 708 BatchGroup *BG = Sci->FreeListInfo.BlockList.front(); 709 Sci->FreeListInfo.BlockList.pop_front(); 710 711 // Block used by `BatchGroup` is from BatchClassId. Turn the block into 712 // `TransferBatch` with single block. 713 TransferBatch *TB = reinterpret_cast<TransferBatch *>(BG); 714 TB->clear(); 715 TB->add( 716 compactPtr(SizeClassMap::BatchClassId, reinterpret_cast<uptr>(TB))); 717 Sci->FreeListInfo.PoppedBlocks += 1; 718 return TB; 719 } 720 721 TransferBatch *B = Batches.front(); 722 Batches.pop_front(); 723 DCHECK_NE(B, nullptr); 724 DCHECK_GT(B->getCount(), 0U); 725 726 if (Batches.empty()) { 727 BatchGroup *BG = Sci->FreeListInfo.BlockList.front(); 728 Sci->FreeListInfo.BlockList.pop_front(); 729 730 // We don't keep BatchGroup with zero blocks to avoid empty-checking while 731 // allocating. Note that block used by constructing BatchGroup is recorded 732 // as free blocks in the last element of BatchGroup::Batches. Which means, 733 // once we pop the last TransferBatch, the block is implicitly 734 // deallocated. 735 if (ClassId != SizeClassMap::BatchClassId) 736 C->deallocate(SizeClassMap::BatchClassId, BG); 737 } 738 739 Sci->FreeListInfo.PoppedBlocks += B->getCount(); 740 return B; 741 } 742 743 NOINLINE bool populateFreeList(CacheT *C, uptr ClassId, SizeClassInfo *Sci) 744 REQUIRES(Sci->Mutex) { 745 uptr Region; 746 uptr Offset; 747 // If the size-class currently has a region associated to it, use it. The 748 // newly created blocks will be located after the currently allocated memory 749 // for that region (up to RegionSize). Otherwise, create a new region, where 750 // the new blocks will be carved from the beginning. 751 if (Sci->CurrentRegion) { 752 Region = Sci->CurrentRegion; 753 DCHECK_GT(Sci->CurrentRegionAllocated, 0U); 754 Offset = Sci->CurrentRegionAllocated; 755 } else { 756 DCHECK_EQ(Sci->CurrentRegionAllocated, 0U); 757 Region = allocateRegion(Sci, ClassId); 758 if (UNLIKELY(!Region)) 759 return false; 760 C->getStats().add(StatMapped, RegionSize); 761 Sci->CurrentRegion = Region; 762 Offset = 0; 763 } 764 765 const uptr Size = getSizeByClassId(ClassId); 766 const u16 MaxCount = TransferBatch::getMaxCached(Size); 767 DCHECK_GT(MaxCount, 0U); 768 // The maximum number of blocks we should carve in the region is dictated 769 // by the maximum number of batches we want to fill, and the amount of 770 // memory left in the current region (we use the lowest of the two). This 771 // will not be 0 as we ensure that a region can at least hold one block (via 772 // static_assert and at the end of this function). 773 const u32 NumberOfBlocks = 774 Min(MaxNumBatches * MaxCount, 775 static_cast<u32>((RegionSize - Offset) / Size)); 776 DCHECK_GT(NumberOfBlocks, 0U); 777 778 constexpr u32 ShuffleArraySize = 779 MaxNumBatches * TransferBatch::MaxNumCached; 780 // Fill the transfer batches and put them in the size-class freelist. We 781 // need to randomize the blocks for security purposes, so we first fill a 782 // local array that we then shuffle before populating the batches. 783 CompactPtrT ShuffleArray[ShuffleArraySize]; 784 DCHECK_LE(NumberOfBlocks, ShuffleArraySize); 785 786 uptr P = Region + Offset; 787 for (u32 I = 0; I < NumberOfBlocks; I++, P += Size) 788 ShuffleArray[I] = reinterpret_cast<CompactPtrT>(P); 789 790 if (ClassId != SizeClassMap::BatchClassId) { 791 u32 N = 1; 792 uptr CurGroup = compactPtrGroupBase(ShuffleArray[0]); 793 for (u32 I = 1; I < NumberOfBlocks; I++) { 794 if (UNLIKELY(compactPtrGroupBase(ShuffleArray[I]) != CurGroup)) { 795 shuffle(ShuffleArray + I - N, N, &Sci->RandState); 796 pushBlocksImpl(C, ClassId, Sci, ShuffleArray + I - N, N, 797 /*SameGroup=*/true); 798 N = 1; 799 CurGroup = compactPtrGroupBase(ShuffleArray[I]); 800 } else { 801 ++N; 802 } 803 } 804 805 shuffle(ShuffleArray + NumberOfBlocks - N, N, &Sci->RandState); 806 pushBlocksImpl(C, ClassId, Sci, &ShuffleArray[NumberOfBlocks - N], N, 807 /*SameGroup=*/true); 808 } else { 809 pushBatchClassBlocks(Sci, ShuffleArray, NumberOfBlocks); 810 } 811 812 // Note that `PushedBlocks` and `PoppedBlocks` are supposed to only record 813 // the requests from `PushBlocks` and `PopBatch` which are external 814 // interfaces. `populateFreeList` is the internal interface so we should set 815 // the values back to avoid incorrectly setting the stats. 816 Sci->FreeListInfo.PushedBlocks -= NumberOfBlocks; 817 818 const uptr AllocatedUser = Size * NumberOfBlocks; 819 C->getStats().add(StatFree, AllocatedUser); 820 DCHECK_LE(Sci->CurrentRegionAllocated + AllocatedUser, RegionSize); 821 // If there is not enough room in the region currently associated to fit 822 // more blocks, we deassociate the region by resetting CurrentRegion and 823 // CurrentRegionAllocated. Otherwise, update the allocated amount. 824 if (RegionSize - (Sci->CurrentRegionAllocated + AllocatedUser) < Size) { 825 Sci->CurrentRegion = 0; 826 Sci->CurrentRegionAllocated = 0; 827 } else { 828 Sci->CurrentRegionAllocated += AllocatedUser; 829 } 830 Sci->AllocatedUser += AllocatedUser; 831 832 return true; 833 } 834 835 void getStats(ScopedString *Str, uptr ClassId, SizeClassInfo *Sci) 836 REQUIRES(Sci->Mutex) { 837 if (Sci->AllocatedUser == 0) 838 return; 839 const uptr BlockSize = getSizeByClassId(ClassId); 840 const uptr InUse = 841 Sci->FreeListInfo.PoppedBlocks - Sci->FreeListInfo.PushedBlocks; 842 const uptr BytesInFreeList = Sci->AllocatedUser - InUse * BlockSize; 843 uptr PushedBytesDelta = 0; 844 if (BytesInFreeList >= Sci->ReleaseInfo.BytesInFreeListAtLastCheckpoint) { 845 PushedBytesDelta = 846 BytesInFreeList - Sci->ReleaseInfo.BytesInFreeListAtLastCheckpoint; 847 } 848 const uptr AvailableChunks = Sci->AllocatedUser / BlockSize; 849 Str->append(" %02zu (%6zu): mapped: %6zuK popped: %7zu pushed: %7zu " 850 "inuse: %6zu avail: %6zu releases: %6zu last released: %6zuK " 851 "latest pushed bytes: %6zuK\n", 852 ClassId, getSizeByClassId(ClassId), Sci->AllocatedUser >> 10, 853 Sci->FreeListInfo.PoppedBlocks, Sci->FreeListInfo.PushedBlocks, 854 InUse, AvailableChunks, Sci->ReleaseInfo.RangesReleased, 855 Sci->ReleaseInfo.LastReleasedBytes >> 10, 856 PushedBytesDelta >> 10); 857 } 858 859 NOINLINE uptr releaseToOSMaybe(SizeClassInfo *Sci, uptr ClassId, 860 ReleaseToOS ReleaseType = ReleaseToOS::Normal) 861 REQUIRES(Sci->Mutex) { 862 const uptr BlockSize = getSizeByClassId(ClassId); 863 const uptr PageSize = getPageSizeCached(); 864 865 DCHECK_GE(Sci->FreeListInfo.PoppedBlocks, Sci->FreeListInfo.PushedBlocks); 866 const uptr BytesInFreeList = 867 Sci->AllocatedUser - 868 (Sci->FreeListInfo.PoppedBlocks - Sci->FreeListInfo.PushedBlocks) * 869 BlockSize; 870 871 if (UNLIKELY(BytesInFreeList == 0)) 872 return 0; 873 874 if (BytesInFreeList <= Sci->ReleaseInfo.BytesInFreeListAtLastCheckpoint) 875 Sci->ReleaseInfo.BytesInFreeListAtLastCheckpoint = BytesInFreeList; 876 877 // Always update `BytesInFreeListAtLastCheckpoint` with the smallest value 878 // so that we won't underestimate the releasable pages. For example, the 879 // following is the region usage, 880 // 881 // BytesInFreeListAtLastCheckpoint AllocatedUser 882 // v v 883 // |---------------------------------------> 884 // ^ ^ 885 // BytesInFreeList ReleaseThreshold 886 // 887 // In general, if we have collected enough bytes and the amount of free 888 // bytes meets the ReleaseThreshold, we will try to do page release. If we 889 // don't update `BytesInFreeListAtLastCheckpoint` when the current 890 // `BytesInFreeList` is smaller, we may take longer time to wait for enough 891 // freed blocks because we miss the bytes between 892 // (BytesInFreeListAtLastCheckpoint - BytesInFreeList). 893 const uptr PushedBytesDelta = 894 BytesInFreeList - Sci->ReleaseInfo.BytesInFreeListAtLastCheckpoint; 895 if (PushedBytesDelta < PageSize && ReleaseType != ReleaseToOS::ForceAll) 896 return 0; 897 898 const bool CheckDensity = 899 isSmallBlock(BlockSize) && ReleaseType != ReleaseToOS::ForceAll; 900 // Releasing smaller blocks is expensive, so we want to make sure that a 901 // significant amount of bytes are free, and that there has been a good 902 // amount of batches pushed to the freelist before attempting to release. 903 if (CheckDensity && ReleaseType == ReleaseToOS::Normal) 904 if (PushedBytesDelta < Sci->AllocatedUser / 16U) 905 return 0; 906 907 if (ReleaseType == ReleaseToOS::Normal) { 908 const s32 IntervalMs = atomic_load_relaxed(&ReleaseToOsIntervalMs); 909 if (IntervalMs < 0) 910 return 0; 911 912 // The constant 8 here is selected from profiling some apps and the number 913 // of unreleased pages in the large size classes is around 16 pages or 914 // more. Choose half of it as a heuristic and which also avoids page 915 // release every time for every pushBlocks() attempt by large blocks. 916 const bool ByPassReleaseInterval = 917 isLargeBlock(BlockSize) && PushedBytesDelta > 8 * PageSize; 918 if (!ByPassReleaseInterval) { 919 if (Sci->ReleaseInfo.LastReleaseAtNs + 920 static_cast<u64>(IntervalMs) * 1000000 > 921 getMonotonicTimeFast()) { 922 // Memory was returned recently. 923 return 0; 924 } 925 } 926 } // if (ReleaseType == ReleaseToOS::Normal) 927 928 const uptr First = Sci->MinRegionIndex; 929 const uptr Last = Sci->MaxRegionIndex; 930 DCHECK_NE(Last, 0U); 931 DCHECK_LE(First, Last); 932 uptr TotalReleasedBytes = 0; 933 const uptr Base = First * RegionSize; 934 const uptr NumberOfRegions = Last - First + 1U; 935 const uptr GroupSize = (1U << GroupSizeLog); 936 const uptr CurGroupBase = 937 compactPtrGroupBase(compactPtr(ClassId, Sci->CurrentRegion)); 938 939 ReleaseRecorder Recorder(Base); 940 PageReleaseContext Context(BlockSize, NumberOfRegions, 941 /*ReleaseSize=*/RegionSize); 942 943 auto DecompactPtr = [](CompactPtrT CompactPtr) { 944 return reinterpret_cast<uptr>(CompactPtr); 945 }; 946 for (BatchGroup &BG : Sci->FreeListInfo.BlockList) { 947 const uptr GroupBase = decompactGroupBase(BG.CompactPtrGroupBase); 948 // The `GroupSize` may not be divided by `BlockSize`, which means there is 949 // an unused space at the end of Region. Exclude that space to avoid 950 // unused page map entry. 951 uptr AllocatedGroupSize = GroupBase == CurGroupBase 952 ? Sci->CurrentRegionAllocated 953 : roundDownSlow(GroupSize, BlockSize); 954 if (AllocatedGroupSize == 0) 955 continue; 956 957 // TransferBatches are pushed in front of BG.Batches. The first one may 958 // not have all caches used. 959 const uptr NumBlocks = (BG.Batches.size() - 1) * BG.MaxCachedPerBatch + 960 BG.Batches.front()->getCount(); 961 const uptr BytesInBG = NumBlocks * BlockSize; 962 963 if (ReleaseType != ReleaseToOS::ForceAll && 964 BytesInBG <= BG.BytesInBGAtLastCheckpoint) { 965 BG.BytesInBGAtLastCheckpoint = BytesInBG; 966 continue; 967 } 968 const uptr PushedBytesDelta = BytesInBG - BG.BytesInBGAtLastCheckpoint; 969 if (ReleaseType != ReleaseToOS::ForceAll && PushedBytesDelta < PageSize) 970 continue; 971 972 // Given the randomness property, we try to release the pages only if the 973 // bytes used by free blocks exceed certain proportion of allocated 974 // spaces. 975 if (CheckDensity && (BytesInBG * 100U) / AllocatedGroupSize < 976 (100U - 1U - BlockSize / 16U)) { 977 continue; 978 } 979 980 // TODO: Consider updating this after page release if `ReleaseRecorder` 981 // can tell the releasd bytes in each group. 982 BG.BytesInBGAtLastCheckpoint = BytesInBG; 983 984 const uptr MaxContainedBlocks = AllocatedGroupSize / BlockSize; 985 const uptr RegionIndex = (GroupBase - Base) / RegionSize; 986 987 if (NumBlocks == MaxContainedBlocks) { 988 for (const auto &It : BG.Batches) 989 for (u16 I = 0; I < It.getCount(); ++I) 990 DCHECK_EQ(compactPtrGroupBase(It.get(I)), BG.CompactPtrGroupBase); 991 992 const uptr To = GroupBase + AllocatedGroupSize; 993 Context.markRangeAsAllCounted(GroupBase, To, GroupBase, RegionIndex, 994 AllocatedGroupSize); 995 } else { 996 DCHECK_LT(NumBlocks, MaxContainedBlocks); 997 998 // Note that we don't always visit blocks in each BatchGroup so that we 999 // may miss the chance of releasing certain pages that cross 1000 // BatchGroups. 1001 Context.markFreeBlocksInRegion(BG.Batches, DecompactPtr, GroupBase, 1002 RegionIndex, AllocatedGroupSize, 1003 /*MayContainLastBlockInRegion=*/true); 1004 } 1005 1006 // We may not be able to do the page release In a rare case that we may 1007 // fail on PageMap allocation. 1008 if (UNLIKELY(!Context.hasBlockMarked())) 1009 return 0; 1010 } 1011 1012 if (!Context.hasBlockMarked()) 1013 return 0; 1014 1015 auto SkipRegion = [this, First, ClassId](uptr RegionIndex) { 1016 ScopedLock L(ByteMapMutex); 1017 return (PossibleRegions[First + RegionIndex] - 1U) != ClassId; 1018 }; 1019 releaseFreeMemoryToOS(Context, Recorder, SkipRegion); 1020 1021 if (Recorder.getReleasedRangesCount() > 0) { 1022 Sci->ReleaseInfo.BytesInFreeListAtLastCheckpoint = BytesInFreeList; 1023 Sci->ReleaseInfo.RangesReleased += Recorder.getReleasedRangesCount(); 1024 Sci->ReleaseInfo.LastReleasedBytes = Recorder.getReleasedBytes(); 1025 TotalReleasedBytes += Sci->ReleaseInfo.LastReleasedBytes; 1026 } 1027 Sci->ReleaseInfo.LastReleaseAtNs = getMonotonicTimeFast(); 1028 1029 return TotalReleasedBytes; 1030 } 1031 1032 SizeClassInfo SizeClassInfoArray[NumClasses] = {}; 1033 1034 HybridMutex ByteMapMutex; 1035 // Track the regions in use, 0 is unused, otherwise store ClassId + 1. 1036 ByteMap PossibleRegions GUARDED_BY(ByteMapMutex) = {}; 1037 atomic_s32 ReleaseToOsIntervalMs = {}; 1038 // Unless several threads request regions simultaneously from different size 1039 // classes, the stash rarely contains more than 1 entry. 1040 static constexpr uptr MaxStashedRegions = 4; 1041 HybridMutex RegionsStashMutex; 1042 uptr NumberOfStashedRegions GUARDED_BY(RegionsStashMutex) = 0; 1043 uptr RegionsStash[MaxStashedRegions] GUARDED_BY(RegionsStashMutex) = {}; 1044 }; 1045 1046 } // namespace scudo 1047 1048 #endif // SCUDO_PRIMARY32_H_ 1049