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 "bytemap.h" 13 #include "common.h" 14 #include "list.h" 15 #include "local_cache.h" 16 #include "memtag.h" 17 #include "options.h" 18 #include "release.h" 19 #include "stats.h" 20 #include "string_utils.h" 21 22 namespace scudo { 23 24 // SizeClassAllocator64 is an allocator tuned for 64-bit address space. 25 // 26 // It starts by reserving NumClasses * 2^RegionSizeLog bytes, equally divided in 27 // Regions, specific to each size class. Note that the base of that mapping is 28 // random (based to the platform specific map() capabilities). If 29 // PrimaryEnableRandomOffset is set, each Region actually starts at a random 30 // offset from its base. 31 // 32 // Regions are mapped incrementally on demand to fulfill allocation requests, 33 // those mappings being split into equally sized Blocks based on the size class 34 // they belong to. The Blocks created are shuffled to prevent predictable 35 // address patterns (the predictability increases with the size of the Blocks). 36 // 37 // The 1st Region (for size class 0) holds the TransferBatches. This is a 38 // structure used to transfer arrays of available pointers from the class size 39 // freelist to the thread specific freelist, and back. 40 // 41 // The memory used by this allocator is never unmapped, but can be partially 42 // released if the platform allows for it. 43 44 template <typename Config> class SizeClassAllocator64 { 45 public: 46 typedef typename Config::PrimaryCompactPtrT CompactPtrT; 47 static const uptr CompactPtrScale = Config::PrimaryCompactPtrScale; 48 typedef typename Config::SizeClassMap SizeClassMap; 49 typedef SizeClassAllocator64<Config> ThisT; 50 typedef SizeClassAllocatorLocalCache<ThisT> CacheT; 51 typedef typename CacheT::TransferBatch TransferBatch; 52 53 static uptr getSizeByClassId(uptr ClassId) { 54 return (ClassId == SizeClassMap::BatchClassId) 55 ? roundUpTo(sizeof(TransferBatch), 1U << CompactPtrScale) 56 : SizeClassMap::getSizeByClassId(ClassId); 57 } 58 59 static bool canAllocate(uptr Size) { return Size <= SizeClassMap::MaxSize; } 60 61 void init(s32 ReleaseToOsInterval) { 62 DCHECK(isAligned(reinterpret_cast<uptr>(this), alignof(ThisT))); 63 DCHECK_EQ(PrimaryBase, 0U); 64 // Reserve the space required for the Primary. 65 PrimaryBase = reinterpret_cast<uptr>( 66 map(nullptr, PrimarySize, nullptr, MAP_NOACCESS, &Data)); 67 68 u32 Seed; 69 const u64 Time = getMonotonicTime(); 70 if (!getRandom(reinterpret_cast<void *>(&Seed), sizeof(Seed))) 71 Seed = static_cast<u32>(Time ^ (PrimaryBase >> 12)); 72 const uptr PageSize = getPageSizeCached(); 73 for (uptr I = 0; I < NumClasses; I++) { 74 RegionInfo *Region = getRegionInfo(I); 75 // The actual start of a region is offset by a random number of pages 76 // when PrimaryEnableRandomOffset is set. 77 Region->RegionBeg = getRegionBaseByClassId(I) + 78 (Config::PrimaryEnableRandomOffset 79 ? ((getRandomModN(&Seed, 16) + 1) * PageSize) 80 : 0); 81 Region->RandState = getRandomU32(&Seed); 82 Region->ReleaseInfo.LastReleaseAtNs = Time; 83 } 84 setOption(Option::ReleaseInterval, static_cast<sptr>(ReleaseToOsInterval)); 85 } 86 87 void unmapTestOnly() { 88 for (uptr I = 0; I < NumClasses; I++) { 89 RegionInfo *Region = getRegionInfo(I); 90 *Region = {}; 91 } 92 unmap(reinterpret_cast<void *>(PrimaryBase), PrimarySize, UNMAP_ALL, &Data); 93 PrimaryBase = 0U; 94 } 95 96 TransferBatch *popBatch(CacheT *C, uptr ClassId) { 97 DCHECK_LT(ClassId, NumClasses); 98 RegionInfo *Region = getRegionInfo(ClassId); 99 ScopedLock L(Region->Mutex); 100 TransferBatch *B = Region->FreeList.front(); 101 if (B) { 102 Region->FreeList.pop_front(); 103 } else { 104 B = populateFreeList(C, ClassId, Region); 105 if (UNLIKELY(!B)) 106 return nullptr; 107 } 108 DCHECK_GT(B->getCount(), 0); 109 Region->Stats.PoppedBlocks += B->getCount(); 110 return B; 111 } 112 113 void pushBatch(uptr ClassId, TransferBatch *B) { 114 DCHECK_GT(B->getCount(), 0); 115 RegionInfo *Region = getRegionInfo(ClassId); 116 ScopedLock L(Region->Mutex); 117 Region->FreeList.push_front(B); 118 Region->Stats.PushedBlocks += B->getCount(); 119 if (ClassId != SizeClassMap::BatchClassId) 120 releaseToOSMaybe(Region, ClassId); 121 } 122 123 void disable() { 124 // The BatchClassId must be locked last since other classes can use it. 125 for (sptr I = static_cast<sptr>(NumClasses) - 1; I >= 0; I--) { 126 if (static_cast<uptr>(I) == SizeClassMap::BatchClassId) 127 continue; 128 getRegionInfo(static_cast<uptr>(I))->Mutex.lock(); 129 } 130 getRegionInfo(SizeClassMap::BatchClassId)->Mutex.lock(); 131 } 132 133 void enable() { 134 getRegionInfo(SizeClassMap::BatchClassId)->Mutex.unlock(); 135 for (uptr I = 0; I < NumClasses; I++) { 136 if (I == SizeClassMap::BatchClassId) 137 continue; 138 getRegionInfo(I)->Mutex.unlock(); 139 } 140 } 141 142 template <typename F> void iterateOverBlocks(F Callback) { 143 for (uptr I = 0; I < NumClasses; I++) { 144 if (I == SizeClassMap::BatchClassId) 145 continue; 146 const RegionInfo *Region = getRegionInfo(I); 147 const uptr BlockSize = getSizeByClassId(I); 148 const uptr From = Region->RegionBeg; 149 const uptr To = From + Region->AllocatedUser; 150 for (uptr Block = From; Block < To; Block += BlockSize) 151 Callback(Block); 152 } 153 } 154 155 void getStats(ScopedString *Str) { 156 // TODO(kostyak): get the RSS per region. 157 uptr TotalMapped = 0; 158 uptr PoppedBlocks = 0; 159 uptr PushedBlocks = 0; 160 for (uptr I = 0; I < NumClasses; I++) { 161 RegionInfo *Region = getRegionInfo(I); 162 if (Region->MappedUser) 163 TotalMapped += Region->MappedUser; 164 PoppedBlocks += Region->Stats.PoppedBlocks; 165 PushedBlocks += Region->Stats.PushedBlocks; 166 } 167 Str->append("Stats: SizeClassAllocator64: %zuM mapped (%zuM rss) in %zu " 168 "allocations; remains %zu\n", 169 TotalMapped >> 20, 0, PoppedBlocks, 170 PoppedBlocks - PushedBlocks); 171 172 for (uptr I = 0; I < NumClasses; I++) 173 getStats(Str, I, 0); 174 } 175 176 bool setOption(Option O, sptr Value) { 177 if (O == Option::ReleaseInterval) { 178 const s32 Interval = Max( 179 Min(static_cast<s32>(Value), Config::PrimaryMaxReleaseToOsIntervalMs), 180 Config::PrimaryMinReleaseToOsIntervalMs); 181 atomic_store_relaxed(&ReleaseToOsIntervalMs, Interval); 182 return true; 183 } 184 // Not supported by the Primary, but not an error either. 185 return true; 186 } 187 188 uptr releaseToOS() { 189 uptr TotalReleasedBytes = 0; 190 for (uptr I = 0; I < NumClasses; I++) { 191 if (I == SizeClassMap::BatchClassId) 192 continue; 193 RegionInfo *Region = getRegionInfo(I); 194 ScopedLock L(Region->Mutex); 195 TotalReleasedBytes += releaseToOSMaybe(Region, I, /*Force=*/true); 196 } 197 return TotalReleasedBytes; 198 } 199 200 const char *getRegionInfoArrayAddress() const { 201 return reinterpret_cast<const char *>(RegionInfoArray); 202 } 203 204 static uptr getRegionInfoArraySize() { return sizeof(RegionInfoArray); } 205 206 uptr getCompactPtrBaseByClassId(uptr ClassId) { 207 // If we are not compacting pointers, base everything off of 0. 208 if (sizeof(CompactPtrT) == sizeof(uptr) && CompactPtrScale == 0) 209 return 0; 210 return getRegionInfo(ClassId)->RegionBeg; 211 } 212 213 CompactPtrT compactPtr(uptr ClassId, uptr Ptr) { 214 DCHECK_LE(ClassId, SizeClassMap::LargestClassId); 215 return compactPtrInternal(getCompactPtrBaseByClassId(ClassId), Ptr); 216 } 217 218 void *decompactPtr(uptr ClassId, CompactPtrT CompactPtr) { 219 DCHECK_LE(ClassId, SizeClassMap::LargestClassId); 220 return reinterpret_cast<void *>( 221 decompactPtrInternal(getCompactPtrBaseByClassId(ClassId), CompactPtr)); 222 } 223 224 static BlockInfo findNearestBlock(const char *RegionInfoData, uptr Ptr) { 225 const RegionInfo *RegionInfoArray = 226 reinterpret_cast<const RegionInfo *>(RegionInfoData); 227 uptr ClassId; 228 uptr MinDistance = -1UL; 229 for (uptr I = 0; I != NumClasses; ++I) { 230 if (I == SizeClassMap::BatchClassId) 231 continue; 232 uptr Begin = RegionInfoArray[I].RegionBeg; 233 uptr End = Begin + RegionInfoArray[I].AllocatedUser; 234 if (Begin > End || End - Begin < SizeClassMap::getSizeByClassId(I)) 235 continue; 236 uptr RegionDistance; 237 if (Begin <= Ptr) { 238 if (Ptr < End) 239 RegionDistance = 0; 240 else 241 RegionDistance = Ptr - End; 242 } else { 243 RegionDistance = Begin - Ptr; 244 } 245 246 if (RegionDistance < MinDistance) { 247 MinDistance = RegionDistance; 248 ClassId = I; 249 } 250 } 251 252 BlockInfo B = {}; 253 if (MinDistance <= 8192) { 254 B.RegionBegin = RegionInfoArray[ClassId].RegionBeg; 255 B.RegionEnd = B.RegionBegin + RegionInfoArray[ClassId].AllocatedUser; 256 B.BlockSize = SizeClassMap::getSizeByClassId(ClassId); 257 B.BlockBegin = 258 B.RegionBegin + uptr(sptr(Ptr - B.RegionBegin) / sptr(B.BlockSize) * 259 sptr(B.BlockSize)); 260 while (B.BlockBegin < B.RegionBegin) 261 B.BlockBegin += B.BlockSize; 262 while (B.RegionEnd < B.BlockBegin + B.BlockSize) 263 B.BlockBegin -= B.BlockSize; 264 } 265 return B; 266 } 267 268 AtomicOptions Options; 269 270 private: 271 static const uptr RegionSize = 1UL << Config::PrimaryRegionSizeLog; 272 static const uptr NumClasses = SizeClassMap::NumClasses; 273 static const uptr PrimarySize = RegionSize * NumClasses; 274 275 static const uptr MapSizeIncrement = Config::PrimaryMapSizeIncrement; 276 // Fill at most this number of batches from the newly map'd memory. 277 static const u32 MaxNumBatches = SCUDO_ANDROID ? 4U : 8U; 278 279 struct RegionStats { 280 uptr PoppedBlocks; 281 uptr PushedBlocks; 282 }; 283 284 struct ReleaseToOsInfo { 285 uptr PushedBlocksAtLastRelease; 286 uptr RangesReleased; 287 uptr LastReleasedBytes; 288 u64 LastReleaseAtNs; 289 }; 290 291 struct UnpaddedRegionInfo { 292 HybridMutex Mutex; 293 SinglyLinkedList<TransferBatch> FreeList; 294 uptr RegionBeg = 0; 295 RegionStats Stats = {}; 296 u32 RandState = 0; 297 uptr MappedUser = 0; // Bytes mapped for user memory. 298 uptr AllocatedUser = 0; // Bytes allocated for user memory. 299 MapPlatformData Data = {}; 300 ReleaseToOsInfo ReleaseInfo = {}; 301 bool Exhausted = false; 302 }; 303 struct RegionInfo : UnpaddedRegionInfo { 304 char Padding[SCUDO_CACHE_LINE_SIZE - 305 (sizeof(UnpaddedRegionInfo) % SCUDO_CACHE_LINE_SIZE)] = {}; 306 }; 307 static_assert(sizeof(RegionInfo) % SCUDO_CACHE_LINE_SIZE == 0, ""); 308 309 uptr PrimaryBase = 0; 310 MapPlatformData Data = {}; 311 atomic_s32 ReleaseToOsIntervalMs = {}; 312 alignas(SCUDO_CACHE_LINE_SIZE) RegionInfo RegionInfoArray[NumClasses]; 313 314 RegionInfo *getRegionInfo(uptr ClassId) { 315 DCHECK_LT(ClassId, NumClasses); 316 return &RegionInfoArray[ClassId]; 317 } 318 319 uptr getRegionBaseByClassId(uptr ClassId) const { 320 return PrimaryBase + (ClassId << Config::PrimaryRegionSizeLog); 321 } 322 323 static CompactPtrT compactPtrInternal(uptr Base, uptr Ptr) { 324 return static_cast<CompactPtrT>((Ptr - Base) >> CompactPtrScale); 325 } 326 327 static uptr decompactPtrInternal(uptr Base, CompactPtrT CompactPtr) { 328 return Base + (static_cast<uptr>(CompactPtr) << CompactPtrScale); 329 } 330 331 NOINLINE TransferBatch *populateFreeList(CacheT *C, uptr ClassId, 332 RegionInfo *Region) { 333 const uptr Size = getSizeByClassId(ClassId); 334 const u32 MaxCount = TransferBatch::getMaxCached(Size); 335 336 const uptr RegionBeg = Region->RegionBeg; 337 const uptr MappedUser = Region->MappedUser; 338 const uptr TotalUserBytes = Region->AllocatedUser + MaxCount * Size; 339 // Map more space for blocks, if necessary. 340 if (TotalUserBytes > MappedUser) { 341 // Do the mmap for the user memory. 342 const uptr MapSize = 343 roundUpTo(TotalUserBytes - MappedUser, MapSizeIncrement); 344 const uptr RegionBase = RegionBeg - getRegionBaseByClassId(ClassId); 345 if (UNLIKELY(RegionBase + MappedUser + MapSize > RegionSize)) { 346 if (!Region->Exhausted) { 347 Region->Exhausted = true; 348 ScopedString Str; 349 getStats(&Str); 350 Str.append( 351 "Scudo OOM: The process has exhausted %zuM for size class %zu.\n", 352 RegionSize >> 20, Size); 353 Str.output(); 354 } 355 return nullptr; 356 } 357 if (MappedUser == 0) 358 Region->Data = Data; 359 if (UNLIKELY(!map( 360 reinterpret_cast<void *>(RegionBeg + MappedUser), MapSize, 361 "scudo:primary", 362 MAP_ALLOWNOMEM | MAP_RESIZABLE | 363 (useMemoryTagging<Config>(Options.load()) ? MAP_MEMTAG : 0), 364 &Region->Data))) 365 return nullptr; 366 Region->MappedUser += MapSize; 367 C->getStats().add(StatMapped, MapSize); 368 } 369 370 const u32 NumberOfBlocks = Min( 371 MaxNumBatches * MaxCount, 372 static_cast<u32>((Region->MappedUser - Region->AllocatedUser) / Size)); 373 DCHECK_GT(NumberOfBlocks, 0); 374 375 constexpr u32 ShuffleArraySize = 376 MaxNumBatches * TransferBatch::MaxNumCached; 377 CompactPtrT ShuffleArray[ShuffleArraySize]; 378 DCHECK_LE(NumberOfBlocks, ShuffleArraySize); 379 380 const uptr CompactPtrBase = getCompactPtrBaseByClassId(ClassId); 381 uptr P = RegionBeg + Region->AllocatedUser; 382 for (u32 I = 0; I < NumberOfBlocks; I++, P += Size) 383 ShuffleArray[I] = compactPtrInternal(CompactPtrBase, P); 384 // No need to shuffle the batches size class. 385 if (ClassId != SizeClassMap::BatchClassId) 386 shuffle(ShuffleArray, NumberOfBlocks, &Region->RandState); 387 for (u32 I = 0; I < NumberOfBlocks;) { 388 TransferBatch *B = 389 C->createBatch(ClassId, reinterpret_cast<void *>(decompactPtrInternal( 390 CompactPtrBase, ShuffleArray[I]))); 391 if (UNLIKELY(!B)) 392 return nullptr; 393 const u32 N = Min(MaxCount, NumberOfBlocks - I); 394 B->setFromArray(&ShuffleArray[I], N); 395 Region->FreeList.push_back(B); 396 I += N; 397 } 398 TransferBatch *B = Region->FreeList.front(); 399 Region->FreeList.pop_front(); 400 DCHECK(B); 401 DCHECK_GT(B->getCount(), 0); 402 403 const uptr AllocatedUser = Size * NumberOfBlocks; 404 C->getStats().add(StatFree, AllocatedUser); 405 Region->AllocatedUser += AllocatedUser; 406 407 return B; 408 } 409 410 void getStats(ScopedString *Str, uptr ClassId, uptr Rss) { 411 RegionInfo *Region = getRegionInfo(ClassId); 412 if (Region->MappedUser == 0) 413 return; 414 const uptr InUse = Region->Stats.PoppedBlocks - Region->Stats.PushedBlocks; 415 const uptr TotalChunks = Region->AllocatedUser / getSizeByClassId(ClassId); 416 Str->append("%s %02zu (%6zu): mapped: %6zuK popped: %7zu pushed: %7zu " 417 "inuse: %6zu total: %6zu rss: %6zuK releases: %6zu last " 418 "released: %6zuK region: 0x%zx (0x%zx)\n", 419 Region->Exhausted ? "F" : " ", ClassId, 420 getSizeByClassId(ClassId), Region->MappedUser >> 10, 421 Region->Stats.PoppedBlocks, Region->Stats.PushedBlocks, InUse, 422 TotalChunks, Rss >> 10, Region->ReleaseInfo.RangesReleased, 423 Region->ReleaseInfo.LastReleasedBytes >> 10, Region->RegionBeg, 424 getRegionBaseByClassId(ClassId)); 425 } 426 427 NOINLINE uptr releaseToOSMaybe(RegionInfo *Region, uptr ClassId, 428 bool Force = false) { 429 const uptr BlockSize = getSizeByClassId(ClassId); 430 const uptr PageSize = getPageSizeCached(); 431 432 DCHECK_GE(Region->Stats.PoppedBlocks, Region->Stats.PushedBlocks); 433 const uptr BytesInFreeList = 434 Region->AllocatedUser - 435 (Region->Stats.PoppedBlocks - Region->Stats.PushedBlocks) * BlockSize; 436 if (BytesInFreeList < PageSize) 437 return 0; // No chance to release anything. 438 const uptr BytesPushed = (Region->Stats.PushedBlocks - 439 Region->ReleaseInfo.PushedBlocksAtLastRelease) * 440 BlockSize; 441 if (BytesPushed < PageSize) 442 return 0; // Nothing new to release. 443 444 // Releasing smaller blocks is expensive, so we want to make sure that a 445 // significant amount of bytes are free, and that there has been a good 446 // amount of batches pushed to the freelist before attempting to release. 447 if (BlockSize < PageSize / 16U) { 448 if (!Force && BytesPushed < Region->AllocatedUser / 16U) 449 return 0; 450 // We want 8x% to 9x% free bytes (the larger the block, the lower the %). 451 if ((BytesInFreeList * 100U) / Region->AllocatedUser < 452 (100U - 1U - BlockSize / 16U)) 453 return 0; 454 } 455 456 if (!Force) { 457 const s32 IntervalMs = atomic_load_relaxed(&ReleaseToOsIntervalMs); 458 if (IntervalMs < 0) 459 return 0; 460 if (Region->ReleaseInfo.LastReleaseAtNs + 461 static_cast<u64>(IntervalMs) * 1000000 > 462 getMonotonicTime()) { 463 return 0; // Memory was returned recently. 464 } 465 } 466 467 ReleaseRecorder Recorder(Region->RegionBeg, &Region->Data); 468 const uptr CompactPtrBase = getCompactPtrBaseByClassId(ClassId); 469 auto DecompactPtr = [CompactPtrBase](CompactPtrT CompactPtr) { 470 return decompactPtrInternal(CompactPtrBase, CompactPtr); 471 }; 472 auto SkipRegion = [](UNUSED uptr RegionIndex) { return false; }; 473 releaseFreeMemoryToOS(Region->FreeList, Region->AllocatedUser, 1U, 474 BlockSize, &Recorder, DecompactPtr, SkipRegion); 475 476 if (Recorder.getReleasedRangesCount() > 0) { 477 Region->ReleaseInfo.PushedBlocksAtLastRelease = 478 Region->Stats.PushedBlocks; 479 Region->ReleaseInfo.RangesReleased += Recorder.getReleasedRangesCount(); 480 Region->ReleaseInfo.LastReleasedBytes = Recorder.getReleasedBytes(); 481 } 482 Region->ReleaseInfo.LastReleaseAtNs = getMonotonicTime(); 483 return Recorder.getReleasedBytes(); 484 } 485 }; 486 487 } // namespace scudo 488 489 #endif // SCUDO_PRIMARY64_H_ 490