blob: 13420bf3d2225908dd137340d911b107c53e4bba [file] [log] [blame]
Dynamic Tools Team517193e2019-09-11 14:48:41 +00001//===-- 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"
Dynamic Tools Team48429c72019-12-04 17:46:15 -080016#include "memtag.h"
Peter Collingbourne3cf60372020-09-24 17:01:24 -070017#include "options.h"
Dynamic Tools Team517193e2019-09-11 14:48:41 +000018#include "release.h"
19#include "stats.h"
20#include "string_utils.h"
21
22namespace 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
Daniel Michael63933f12021-06-07 09:10:46 -070028// 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.
Dynamic Tools Team517193e2019-09-11 14:48:41 +000031//
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
Peter Collingbourne6be49192020-12-15 14:26:10 -080044template <typename Config> class SizeClassAllocator64 {
Dynamic Tools Team517193e2019-09-11 14:48:41 +000045public:
Kostya Kortchinskyc9369542021-02-10 10:17:18 -080046 typedef typename Config::PrimaryCompactPtrT CompactPtrT;
47 static const uptr CompactPtrScale = Config::PrimaryCompactPtrScale;
Peter Collingbourne6be49192020-12-15 14:26:10 -080048 typedef typename Config::SizeClassMap SizeClassMap;
49 typedef SizeClassAllocator64<Config> ThisT;
Dynamic Tools Team517193e2019-09-11 14:48:41 +000050 typedef SizeClassAllocatorLocalCache<ThisT> CacheT;
51 typedef typename CacheT::TransferBatch TransferBatch;
52
53 static uptr getSizeByClassId(uptr ClassId) {
54 return (ClassId == SizeClassMap::BatchClassId)
Kostya Kortchinsky01678932021-04-14 21:09:20 -070055 ? roundUpTo(sizeof(TransferBatch), 1U << CompactPtrScale)
Dynamic Tools Team517193e2019-09-11 14:48:41 +000056 : SizeClassMap::getSizeByClassId(ClassId);
57 }
58
59 static bool canAllocate(uptr Size) { return Size <= SizeClassMap::MaxSize; }
60
Kostya Kortchinsky4a435d22021-05-25 15:00:58 -070061 void init(s32 ReleaseToOsInterval) {
Kostya Kortchinsky43839dc2021-06-16 10:51:51 -070062 DCHECK(isAligned(reinterpret_cast<uptr>(this), alignof(ThisT)));
Kostya Kortchinsky4a435d22021-05-25 15:00:58 -070063 DCHECK_EQ(PrimaryBase, 0U);
Dynamic Tools Team517193e2019-09-11 14:48:41 +000064 // Reserve the space required for the Primary.
65 PrimaryBase = reinterpret_cast<uptr>(
Peter Collingbournecc3d4932020-12-21 18:39:03 -080066 map(nullptr, PrimarySize, nullptr, MAP_NOACCESS, &Data));
Dynamic Tools Team517193e2019-09-11 14:48:41 +000067
Dynamic Tools Team517193e2019-09-11 14:48:41 +000068 u32 Seed;
Dynamic Tools Team6a8384a2020-03-03 11:16:31 -080069 const u64 Time = getMonotonicTime();
Kostya Kortchinskyc9369542021-02-10 10:17:18 -080070 if (!getRandom(reinterpret_cast<void *>(&Seed), sizeof(Seed)))
Dynamic Tools Team6a8384a2020-03-03 11:16:31 -080071 Seed = static_cast<u32>(Time ^ (PrimaryBase >> 12));
Dynamic Tools Team517193e2019-09-11 14:48:41 +000072 const uptr PageSize = getPageSizeCached();
73 for (uptr I = 0; I < NumClasses; I++) {
74 RegionInfo *Region = getRegionInfo(I);
Daniel Michael63933f12021-06-07 09:10:46 -070075 // 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);
Dynamic Tools Team6a8384a2020-03-03 11:16:31 -080081 Region->RandState = getRandomU32(&Seed);
Kostya Kortchinsky156d47c2020-12-11 14:04:47 -080082 Region->ReleaseInfo.LastReleaseAtNs = Time;
Dynamic Tools Team517193e2019-09-11 14:48:41 +000083 }
Kostya Kortchinskyc72ca562020-07-27 09:13:42 -070084 setOption(Option::ReleaseInterval, static_cast<sptr>(ReleaseToOsInterval));
Dynamic Tools Team517193e2019-09-11 14:48:41 +000085 }
Dynamic Tools Team517193e2019-09-11 14:48:41 +000086
87 void unmapTestOnly() {
Kostya Kortchinsky4a435d22021-05-25 15:00:58 -070088 for (uptr I = 0; I < NumClasses; I++) {
89 RegionInfo *Region = getRegionInfo(I);
90 *Region = {};
91 }
Dynamic Tools Team517193e2019-09-11 14:48:41 +000092 unmap(reinterpret_cast<void *>(PrimaryBase), PrimarySize, UNMAP_ALL, &Data);
Kostya Kortchinsky4a435d22021-05-25 15:00:58 -070093 PrimaryBase = 0U;
Dynamic Tools Team517193e2019-09-11 14:48:41 +000094 }
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();
Kostya Kortchinsky156d47c2020-12-11 14:04:47 -0800119 if (ClassId != SizeClassMap::BatchClassId)
Dynamic Tools Team517193e2019-09-11 14:48:41 +0000120 releaseToOSMaybe(Region, ClassId);
121 }
122
123 void disable() {
Dynamic Tools Team83eaa512020-01-09 11:43:16 -0800124 // 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();
Dynamic Tools Team517193e2019-09-11 14:48:41 +0000131 }
132
133 void enable() {
Dynamic Tools Team83eaa512020-01-09 11:43:16 -0800134 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 }
Dynamic Tools Team517193e2019-09-11 14:48:41 +0000140 }
141
Dynamic Tools Team2e7fec22020-02-16 15:29:46 -0800142 template <typename F> void iterateOverBlocks(F Callback) {
Dynamic Tools Team517193e2019-09-11 14:48:41 +0000143 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
Dynamic Tools Team2e7fec22020-02-16 15:29:46 -0800155 void getStats(ScopedString *Str) {
Dynamic Tools Team517193e2019-09-11 14:48:41 +0000156 // 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 }
Dynamic Tools Team3e8c65b2019-10-18 20:00:32 +0000167 Str->append("Stats: SizeClassAllocator64: %zuM mapped (%zuM rss) in %zu "
168 "allocations; remains %zu\n",
169 TotalMapped >> 20, 0, PoppedBlocks,
170 PoppedBlocks - PushedBlocks);
Dynamic Tools Team517193e2019-09-11 14:48:41 +0000171
172 for (uptr I = 0; I < NumClasses; I++)
Dynamic Tools Team3e8c65b2019-10-18 20:00:32 +0000173 getStats(Str, I, 0);
Dynamic Tools Team517193e2019-09-11 14:48:41 +0000174 }
175
Kostya Kortchinskyc72ca562020-07-27 09:13:42 -0700176 bool setOption(Option O, sptr Value) {
177 if (O == Option::ReleaseInterval) {
Peter Collingbourne6be49192020-12-15 14:26:10 -0800178 const s32 Interval = Max(
179 Min(static_cast<s32>(Value), Config::PrimaryMaxReleaseToOsIntervalMs),
180 Config::PrimaryMinReleaseToOsIntervalMs);
Kostya Kortchinskya51a8922020-11-02 14:27:11 -0800181 atomic_store_relaxed(&ReleaseToOsIntervalMs, Interval);
Kostya Kortchinskyc72ca562020-07-27 09:13:42 -0700182 return true;
Dynamic Tools Team26387462020-02-14 12:24:03 -0800183 }
Kostya Kortchinskyc72ca562020-07-27 09:13:42 -0700184 // Not supported by the Primary, but not an error either.
185 return true;
Dynamic Tools Team26387462020-02-14 12:24:03 -0800186 }
187
Dynamic Tools Teamc283bab2019-10-07 17:37:39 +0000188 uptr releaseToOS() {
189 uptr TotalReleasedBytes = 0;
Dynamic Tools Team517193e2019-09-11 14:48:41 +0000190 for (uptr I = 0; I < NumClasses; I++) {
Kostya Kortchinsky156d47c2020-12-11 14:04:47 -0800191 if (I == SizeClassMap::BatchClassId)
192 continue;
Dynamic Tools Team517193e2019-09-11 14:48:41 +0000193 RegionInfo *Region = getRegionInfo(I);
194 ScopedLock L(Region->Mutex);
Dynamic Tools Teamc283bab2019-10-07 17:37:39 +0000195 TotalReleasedBytes += releaseToOSMaybe(Region, I, /*Force=*/true);
Dynamic Tools Team517193e2019-09-11 14:48:41 +0000196 }
Dynamic Tools Teamc283bab2019-10-07 17:37:39 +0000197 return TotalReleasedBytes;
Dynamic Tools Team517193e2019-09-11 14:48:41 +0000198 }
199
Dynamic Tools Team0d7d2ae2020-04-21 15:30:50 -0700200 const char *getRegionInfoArrayAddress() const {
201 return reinterpret_cast<const char *>(RegionInfoArray);
202 }
203
Kostya Kortchinsky394cc822020-06-17 10:31:53 -0700204 static uptr getRegionInfoArraySize() { return sizeof(RegionInfoArray); }
Dynamic Tools Team0d7d2ae2020-04-21 15:30:50 -0700205
Kostya Kortchinskyc9369542021-02-10 10:17:18 -0800206 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
Dynamic Tools Team0d7d2ae2020-04-21 15:30:50 -0700224 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
Peter Collingbourne3cf60372020-09-24 17:01:24 -0700268 AtomicOptions Options;
269
Dynamic Tools Team517193e2019-09-11 14:48:41 +0000270private:
Peter Collingbourne6be49192020-12-15 14:26:10 -0800271 static const uptr RegionSize = 1UL << Config::PrimaryRegionSizeLog;
Dynamic Tools Team517193e2019-09-11 14:48:41 +0000272 static const uptr NumClasses = SizeClassMap::NumClasses;
273 static const uptr PrimarySize = RegionSize * NumClasses;
274
Daniel Michael63933f12021-06-07 09:10:46 -0700275 static const uptr MapSizeIncrement = Config::PrimaryMapSizeIncrement;
Dynamic Tools Teamaa152462019-11-19 10:18:38 -0800276 // Fill at most this number of batches from the newly map'd memory.
Dynamic Tools Team6a8384a2020-03-03 11:16:31 -0800277 static const u32 MaxNumBatches = SCUDO_ANDROID ? 4U : 8U;
Dynamic Tools Team517193e2019-09-11 14:48:41 +0000278
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
Dynamic Tools Team0d7d2ae2020-04-21 15:30:50 -0700291 struct UnpaddedRegionInfo {
Dynamic Tools Team517193e2019-09-11 14:48:41 +0000292 HybridMutex Mutex;
Dynamic Tools Team35997702019-10-28 15:06:10 -0700293 SinglyLinkedList<TransferBatch> FreeList;
Vitaly Buka5d3d7272021-04-29 01:19:51 -0700294 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;
Dynamic Tools Team517193e2019-09-11 14:48:41 +0000302 };
Dynamic Tools Team0d7d2ae2020-04-21 15:30:50 -0700303 struct RegionInfo : UnpaddedRegionInfo {
304 char Padding[SCUDO_CACHE_LINE_SIZE -
Vitaly Buka5d3d7272021-04-29 01:19:51 -0700305 (sizeof(UnpaddedRegionInfo) % SCUDO_CACHE_LINE_SIZE)] = {};
Dynamic Tools Team0d7d2ae2020-04-21 15:30:50 -0700306 };
Dynamic Tools Team09e6d482019-11-26 18:18:14 -0800307 static_assert(sizeof(RegionInfo) % SCUDO_CACHE_LINE_SIZE == 0, "");
Dynamic Tools Team517193e2019-09-11 14:48:41 +0000308
Vitaly Buka5d3d7272021-04-29 01:19:51 -0700309 uptr PrimaryBase = 0;
310 MapPlatformData Data = {};
311 atomic_s32 ReleaseToOsIntervalMs = {};
Dynamic Tools Team0d7d2ae2020-04-21 15:30:50 -0700312 alignas(SCUDO_CACHE_LINE_SIZE) RegionInfo RegionInfoArray[NumClasses];
Dynamic Tools Team517193e2019-09-11 14:48:41 +0000313
Dynamic Tools Team2e7fec22020-02-16 15:29:46 -0800314 RegionInfo *getRegionInfo(uptr ClassId) {
Dynamic Tools Team517193e2019-09-11 14:48:41 +0000315 DCHECK_LT(ClassId, NumClasses);
316 return &RegionInfoArray[ClassId];
317 }
318
319 uptr getRegionBaseByClassId(uptr ClassId) const {
Peter Collingbourne6be49192020-12-15 14:26:10 -0800320 return PrimaryBase + (ClassId << Config::PrimaryRegionSizeLog);
Dynamic Tools Team517193e2019-09-11 14:48:41 +0000321 }
322
Kostya Kortchinskyc9369542021-02-10 10:17:18 -0800323 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
Dynamic Tools Team517193e2019-09-11 14:48:41 +0000331 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.
Kostya Kortchinskyc9369542021-02-10 10:17:18 -0800340 if (TotalUserBytes > MappedUser) {
Dynamic Tools Team517193e2019-09-11 14:48:41 +0000341 // Do the mmap for the user memory.
Kostya Kortchinskyc9369542021-02-10 10:17:18 -0800342 const uptr MapSize =
Dynamic Tools Team517193e2019-09-11 14:48:41 +0000343 roundUpTo(TotalUserBytes - MappedUser, MapSizeIncrement);
344 const uptr RegionBase = RegionBeg - getRegionBaseByClassId(ClassId);
Kostya Kortchinskyc9369542021-02-10 10:17:18 -0800345 if (UNLIKELY(RegionBase + MappedUser + MapSize > RegionSize)) {
Dynamic Tools Team517193e2019-09-11 14:48:41 +0000346 if (!Region->Exhausted) {
347 Region->Exhausted = true;
Kostya Kortchinsky53aea5c2021-06-03 12:11:05 -0700348 ScopedString Str;
Dynamic Tools Team3e8c65b2019-10-18 20:00:32 +0000349 getStats(&Str);
350 Str.append(
Kostya Kortchinskye7788062020-11-03 11:21:15 -0800351 "Scudo OOM: The process has exhausted %zuM for size class %zu.\n",
Dynamic Tools Team517193e2019-09-11 14:48:41 +0000352 RegionSize >> 20, Size);
Dynamic Tools Team3e8c65b2019-10-18 20:00:32 +0000353 Str.output();
Dynamic Tools Team517193e2019-09-11 14:48:41 +0000354 }
355 return nullptr;
356 }
Kostya Kortchinskye7788062020-11-03 11:21:15 -0800357 if (MappedUser == 0)
Dynamic Tools Team517193e2019-09-11 14:48:41 +0000358 Region->Data = Data;
Kostya Kortchinskyc9369542021-02-10 10:17:18 -0800359 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)))
Dynamic Tools Team517193e2019-09-11 14:48:41 +0000365 return nullptr;
Kostya Kortchinskyc9369542021-02-10 10:17:18 -0800366 Region->MappedUser += MapSize;
367 C->getStats().add(StatMapped, MapSize);
Dynamic Tools Team517193e2019-09-11 14:48:41 +0000368 }
369
Dynamic Tools Teamaa152462019-11-19 10:18:38 -0800370 const u32 NumberOfBlocks = Min(
371 MaxNumBatches * MaxCount,
372 static_cast<u32>((Region->MappedUser - Region->AllocatedUser) / Size));
Dynamic Tools Team517193e2019-09-11 14:48:41 +0000373 DCHECK_GT(NumberOfBlocks, 0);
374
Dynamic Tools Teamaa152462019-11-19 10:18:38 -0800375 constexpr u32 ShuffleArraySize =
376 MaxNumBatches * TransferBatch::MaxNumCached;
Kostya Kortchinskyc9369542021-02-10 10:17:18 -0800377 CompactPtrT ShuffleArray[ShuffleArraySize];
Kostya Kortchinskye7788062020-11-03 11:21:15 -0800378 DCHECK_LE(NumberOfBlocks, ShuffleArraySize);
379
Kostya Kortchinskyc9369542021-02-10 10:17:18 -0800380 const uptr CompactPtrBase = getCompactPtrBaseByClassId(ClassId);
Kostya Kortchinskye7788062020-11-03 11:21:15 -0800381 uptr P = RegionBeg + Region->AllocatedUser;
382 for (u32 I = 0; I < NumberOfBlocks; I++, P += Size)
Kostya Kortchinskyc9369542021-02-10 10:17:18 -0800383 ShuffleArray[I] = compactPtrInternal(CompactPtrBase, P);
Kostya Kortchinskye7788062020-11-03 11:21:15 -0800384 // 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;) {
Kostya Kortchinskyc9369542021-02-10 10:17:18 -0800388 TransferBatch *B =
389 C->createBatch(ClassId, reinterpret_cast<void *>(decompactPtrInternal(
390 CompactPtrBase, ShuffleArray[I])));
Kostya Kortchinskye7788062020-11-03 11:21:15 -0800391 if (UNLIKELY(!B))
Dynamic Tools Team517193e2019-09-11 14:48:41 +0000392 return nullptr;
Kostya Kortchinskye7788062020-11-03 11:21:15 -0800393 const u32 N = Min(MaxCount, NumberOfBlocks - I);
394 B->setFromArray(&ShuffleArray[I], N);
Dynamic Tools Teamaa152462019-11-19 10:18:38 -0800395 Region->FreeList.push_back(B);
Kostya Kortchinskye7788062020-11-03 11:21:15 -0800396 I += N;
Dynamic Tools Teamaa152462019-11-19 10:18:38 -0800397 }
Kostya Kortchinskye7788062020-11-03 11:21:15 -0800398 TransferBatch *B = Region->FreeList.front();
399 Region->FreeList.pop_front();
400 DCHECK(B);
Dynamic Tools Team517193e2019-09-11 14:48:41 +0000401 DCHECK_GT(B->getCount(), 0);
402
Kostya Kortchinskye7788062020-11-03 11:21:15 -0800403 const uptr AllocatedUser = Size * NumberOfBlocks;
Dynamic Tools Team517193e2019-09-11 14:48:41 +0000404 C->getStats().add(StatFree, AllocatedUser);
405 Region->AllocatedUser += AllocatedUser;
Dynamic Tools Team517193e2019-09-11 14:48:41 +0000406
407 return B;
408 }
409
Dynamic Tools Team2e7fec22020-02-16 15:29:46 -0800410 void getStats(ScopedString *Str, uptr ClassId, uptr Rss) {
Dynamic Tools Team517193e2019-09-11 14:48:41 +0000411 RegionInfo *Region = getRegionInfo(ClassId);
412 if (Region->MappedUser == 0)
413 return;
414 const uptr InUse = Region->Stats.PoppedBlocks - Region->Stats.PushedBlocks;
Dynamic Tools Teamc283bab2019-10-07 17:37:39 +0000415 const uptr TotalChunks = Region->AllocatedUser / getSizeByClassId(ClassId);
Dynamic Tools Team3e8c65b2019-10-18 20:00:32 +0000416 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));
Dynamic Tools Team517193e2019-09-11 14:48:41 +0000425 }
426
Dynamic Tools Teamc283bab2019-10-07 17:37:39 +0000427 NOINLINE uptr releaseToOSMaybe(RegionInfo *Region, uptr ClassId,
Dynamic Tools Team517193e2019-09-11 14:48:41 +0000428 bool Force = false) {
429 const uptr BlockSize = getSizeByClassId(ClassId);
430 const uptr PageSize = getPageSizeCached();
431
Kostya Kortchinsky156d47c2020-12-11 14:04:47 -0800432 DCHECK_GE(Region->Stats.PoppedBlocks, Region->Stats.PushedBlocks);
Dynamic Tools Teamc283bab2019-10-07 17:37:39 +0000433 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.
Dynamic Tools Teamebcf82d2020-02-25 14:23:34 -0800438 const uptr BytesPushed = (Region->Stats.PushedBlocks -
439 Region->ReleaseInfo.PushedBlocksAtLastRelease) *
440 BlockSize;
441 if (BytesPushed < PageSize)
Dynamic Tools Teamc283bab2019-10-07 17:37:39 +0000442 return 0; // Nothing new to release.
Dynamic Tools Team517193e2019-09-11 14:48:41 +0000443
Kostya Kortchinsky394cc822020-06-17 10:31:53 -0700444 // 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;
Kostya Kortchinskyc9369542021-02-10 10:17:18 -0800450 // We want 8x% to 9x% free bytes (the larger the block, the lower the %).
Kostya Kortchinsky394cc822020-06-17 10:31:53 -0700451 if ((BytesInFreeList * 100U) / Region->AllocatedUser <
452 (100U - 1U - BlockSize / 16U))
453 return 0;
454 }
455
Dynamic Tools Team517193e2019-09-11 14:48:41 +0000456 if (!Force) {
Kostya Kortchinskya51a8922020-11-02 14:27:11 -0800457 const s32 IntervalMs = atomic_load_relaxed(&ReleaseToOsIntervalMs);
Dynamic Tools Team517193e2019-09-11 14:48:41 +0000458 if (IntervalMs < 0)
Dynamic Tools Teamc283bab2019-10-07 17:37:39 +0000459 return 0;
Dynamic Tools Team517193e2019-09-11 14:48:41 +0000460 if (Region->ReleaseInfo.LastReleaseAtNs +
Dynamic Tools Teamc5d5abc2020-01-27 14:03:21 -0800461 static_cast<u64>(IntervalMs) * 1000000 >
Dynamic Tools Team517193e2019-09-11 14:48:41 +0000462 getMonotonicTime()) {
Dynamic Tools Teamc283bab2019-10-07 17:37:39 +0000463 return 0; // Memory was returned recently.
Dynamic Tools Team517193e2019-09-11 14:48:41 +0000464 }
465 }
466
467 ReleaseRecorder Recorder(Region->RegionBeg, &Region->Data);
Kostya Kortchinskyc9369542021-02-10 10:17:18 -0800468 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);
Dynamic Tools Team517193e2019-09-11 14:48:41 +0000475
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();
Dynamic Tools Teamc283bab2019-10-07 17:37:39 +0000483 return Recorder.getReleasedBytes();
Dynamic Tools Team517193e2019-09-11 14:48:41 +0000484 }
485};
486
487} // namespace scudo
488
489#endif // SCUDO_PRIMARY64_H_