primary32.h 44 KB

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