blob: 96fd1e6d55f2b72af4cc63008c8709d238d5e5d8 [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"
16#include "release.h"
17#include "stats.h"
18#include "string_utils.h"
19
20namespace scudo {
21
22// SizeClassAllocator64 is an allocator tuned for 64-bit address space.
23//
24// It starts by reserving NumClasses * 2^RegionSizeLog bytes, equally divided in
25// Regions, specific to each size class. Note that the base of that mapping is
26// random (based to the platform specific map() capabilities), and that each
27// Region actually starts at a random offset from its base.
28//
29// Regions are mapped incrementally on demand to fulfill allocation requests,
30// those mappings being split into equally sized Blocks based on the size class
31// they belong to. The Blocks created are shuffled to prevent predictable
32// address patterns (the predictability increases with the size of the Blocks).
33//
34// The 1st Region (for size class 0) holds the TransferBatches. This is a
35// structure used to transfer arrays of available pointers from the class size
36// freelist to the thread specific freelist, and back.
37//
38// The memory used by this allocator is never unmapped, but can be partially
39// released if the platform allows for it.
40
41template <class SizeClassMapT, uptr RegionSizeLog> class SizeClassAllocator64 {
42public:
43 typedef SizeClassMapT SizeClassMap;
44 typedef SizeClassAllocator64<SizeClassMap, RegionSizeLog> ThisT;
45 typedef SizeClassAllocatorLocalCache<ThisT> CacheT;
46 typedef typename CacheT::TransferBatch TransferBatch;
47
48 static uptr getSizeByClassId(uptr ClassId) {
49 return (ClassId == SizeClassMap::BatchClassId)
50 ? sizeof(TransferBatch)
51 : SizeClassMap::getSizeByClassId(ClassId);
52 }
53
54 static bool canAllocate(uptr Size) { return Size <= SizeClassMap::MaxSize; }
55
56 void initLinkerInitialized(s32 ReleaseToOsInterval) {
57 // Reserve the space required for the Primary.
58 PrimaryBase = reinterpret_cast<uptr>(
59 map(nullptr, PrimarySize, "scudo:primary", MAP_NOACCESS, &Data));
60
61 RegionInfoArray = reinterpret_cast<RegionInfo *>(
62 map(nullptr, sizeof(RegionInfo) * NumClasses, "scudo:regioninfo"));
63 DCHECK_EQ(reinterpret_cast<uptr>(RegionInfoArray) % SCUDO_CACHE_LINE_SIZE,
64 0);
65
66 u32 Seed;
67 if (UNLIKELY(!getRandom(reinterpret_cast<void *>(&Seed), sizeof(Seed))))
68 Seed = static_cast<u32>(getMonotonicTime() ^ (PrimaryBase >> 12));
69 const uptr PageSize = getPageSizeCached();
70 for (uptr I = 0; I < NumClasses; I++) {
71 RegionInfo *Region = getRegionInfo(I);
72 // The actual start of a region is offseted by a random number of pages.
73 Region->RegionBeg =
74 getRegionBaseByClassId(I) + (getRandomModN(&Seed, 16) + 1) * PageSize;
75 // Releasing smaller size classes doesn't necessarily yield to a
76 // meaningful RSS impact: there are more blocks per page, they are
77 // randomized around, and thus pages are less likely to be entirely empty.
78 // On top of this, attempting to release those require more iterations and
79 // memory accesses which ends up being fairly costly. The current lower
80 // limit is mostly arbitrary and based on empirical observations.
81 // TODO(kostyak): make the lower limit a runtime option
82 Region->CanRelease = (ReleaseToOsInterval > 0) &&
83 (I != SizeClassMap::BatchClassId) &&
84 (getSizeByClassId(I) >= (PageSize / 16));
85 Region->RandState = getRandomU32(&Seed);
86 }
87 ReleaseToOsIntervalMs = ReleaseToOsInterval;
88 }
89 void init(s32 ReleaseToOsInterval) {
90 memset(this, 0, sizeof(*this));
91 initLinkerInitialized(ReleaseToOsInterval);
92 }
93
94 void unmapTestOnly() {
95 unmap(reinterpret_cast<void *>(PrimaryBase), PrimarySize, UNMAP_ALL, &Data);
96 unmap(reinterpret_cast<void *>(RegionInfoArray),
97 sizeof(RegionInfo) * NumClasses);
98 }
99
100 TransferBatch *popBatch(CacheT *C, uptr ClassId) {
101 DCHECK_LT(ClassId, NumClasses);
102 RegionInfo *Region = getRegionInfo(ClassId);
103 ScopedLock L(Region->Mutex);
104 TransferBatch *B = Region->FreeList.front();
105 if (B) {
106 Region->FreeList.pop_front();
107 } else {
108 B = populateFreeList(C, ClassId, Region);
109 if (UNLIKELY(!B))
110 return nullptr;
111 }
112 DCHECK_GT(B->getCount(), 0);
113 Region->Stats.PoppedBlocks += B->getCount();
114 return B;
115 }
116
117 void pushBatch(uptr ClassId, TransferBatch *B) {
118 DCHECK_GT(B->getCount(), 0);
119 RegionInfo *Region = getRegionInfo(ClassId);
120 ScopedLock L(Region->Mutex);
121 Region->FreeList.push_front(B);
122 Region->Stats.PushedBlocks += B->getCount();
123 if (Region->CanRelease)
124 releaseToOSMaybe(Region, ClassId);
125 }
126
127 void disable() {
128 for (uptr I = 0; I < NumClasses; I++)
129 getRegionInfo(I)->Mutex.lock();
130 }
131
132 void enable() {
133 for (sptr I = static_cast<sptr>(NumClasses) - 1; I >= 0; I--)
134 getRegionInfo(static_cast<uptr>(I))->Mutex.unlock();
135 }
136
137 template <typename F> void iterateOverBlocks(F Callback) const {
138 for (uptr I = 0; I < NumClasses; I++) {
139 if (I == SizeClassMap::BatchClassId)
140 continue;
141 const RegionInfo *Region = getRegionInfo(I);
142 const uptr BlockSize = getSizeByClassId(I);
143 const uptr From = Region->RegionBeg;
144 const uptr To = From + Region->AllocatedUser;
145 for (uptr Block = From; Block < To; Block += BlockSize)
146 Callback(Block);
147 }
148 }
149
150 void printStats() const {
151 // TODO(kostyak): get the RSS per region.
152 uptr TotalMapped = 0;
153 uptr PoppedBlocks = 0;
154 uptr PushedBlocks = 0;
155 for (uptr I = 0; I < NumClasses; I++) {
156 RegionInfo *Region = getRegionInfo(I);
157 if (Region->MappedUser)
158 TotalMapped += Region->MappedUser;
159 PoppedBlocks += Region->Stats.PoppedBlocks;
160 PushedBlocks += Region->Stats.PushedBlocks;
161 }
162 Printf("Stats: Primary64: %zuM mapped (%zuM rss) in %zu allocations; "
163 "remains %zu\n",
164 TotalMapped >> 20, 0, PoppedBlocks, PoppedBlocks - PushedBlocks);
165
166 for (uptr I = 0; I < NumClasses; I++)
167 printStats(I, 0);
168 }
169
170 void releaseToOS() {
171 for (uptr I = 0; I < NumClasses; I++) {
172 if (I == SizeClassMap::BatchClassId)
173 continue;
174 RegionInfo *Region = getRegionInfo(I);
175 ScopedLock L(Region->Mutex);
176 releaseToOSMaybe(Region, I, /*Force=*/true);
177 }
178 }
179
180private:
181 static const uptr RegionSize = 1UL << RegionSizeLog;
182 static const uptr NumClasses = SizeClassMap::NumClasses;
183 static const uptr PrimarySize = RegionSize * NumClasses;
184
185 // Call map for user memory with at least this size.
186 static const uptr MapSizeIncrement = 1UL << 17;
187
188 struct RegionStats {
189 uptr PoppedBlocks;
190 uptr PushedBlocks;
191 };
192
193 struct ReleaseToOsInfo {
194 uptr PushedBlocksAtLastRelease;
195 uptr RangesReleased;
196 uptr LastReleasedBytes;
197 u64 LastReleaseAtNs;
198 };
199
200 struct ALIGNED(SCUDO_CACHE_LINE_SIZE) RegionInfo {
201 HybridMutex Mutex;
202 IntrusiveList<TransferBatch> FreeList;
203 RegionStats Stats;
204 bool CanRelease;
205 bool Exhausted;
206 u32 RandState;
207 uptr RegionBeg;
208 uptr MappedUser; // Bytes mapped for user memory.
209 uptr AllocatedUser; // Bytes allocated for user memory.
210 MapPlatformData Data;
211 ReleaseToOsInfo ReleaseInfo;
212 };
213 COMPILER_CHECK(sizeof(RegionInfo) % SCUDO_CACHE_LINE_SIZE == 0);
214
215 uptr PrimaryBase;
216 RegionInfo *RegionInfoArray;
217 MapPlatformData Data;
218 s32 ReleaseToOsIntervalMs;
219
220 RegionInfo *getRegionInfo(uptr ClassId) const {
221 DCHECK_LT(ClassId, NumClasses);
222 return &RegionInfoArray[ClassId];
223 }
224
225 uptr getRegionBaseByClassId(uptr ClassId) const {
226 return PrimaryBase + (ClassId << RegionSizeLog);
227 }
228
229 bool populateBatches(CacheT *C, RegionInfo *Region, uptr ClassId,
230 TransferBatch **CurrentBatch, u32 MaxCount,
231 void **PointersArray, u32 Count) {
232 // No need to shuffle the batches size class.
233 if (ClassId != SizeClassMap::BatchClassId)
234 shuffle(PointersArray, Count, &Region->RandState);
235 TransferBatch *B = *CurrentBatch;
236 for (uptr I = 0; I < Count; I++) {
237 if (B && B->getCount() == MaxCount) {
238 Region->FreeList.push_back(B);
239 B = nullptr;
240 }
241 if (!B) {
242 B = C->createBatch(ClassId, PointersArray[I]);
243 if (UNLIKELY(!B))
244 return false;
245 B->clear();
246 }
247 B->add(PointersArray[I]);
248 }
249 *CurrentBatch = B;
250 return true;
251 }
252
253 NOINLINE TransferBatch *populateFreeList(CacheT *C, uptr ClassId,
254 RegionInfo *Region) {
255 const uptr Size = getSizeByClassId(ClassId);
256 const u32 MaxCount = TransferBatch::getMaxCached(Size);
257
258 const uptr RegionBeg = Region->RegionBeg;
259 const uptr MappedUser = Region->MappedUser;
260 const uptr TotalUserBytes = Region->AllocatedUser + MaxCount * Size;
261 // Map more space for blocks, if necessary.
262 if (LIKELY(TotalUserBytes > MappedUser)) {
263 // Do the mmap for the user memory.
264 const uptr UserMapSize =
265 roundUpTo(TotalUserBytes - MappedUser, MapSizeIncrement);
266 const uptr RegionBase = RegionBeg - getRegionBaseByClassId(ClassId);
267 if (UNLIKELY(RegionBase + MappedUser + UserMapSize > RegionSize)) {
268 if (!Region->Exhausted) {
269 Region->Exhausted = true;
270 printStats();
271 Printf(
272 "Scudo OOM: The process has Exhausted %zuM for size class %zu.\n",
273 RegionSize >> 20, Size);
274 }
275 return nullptr;
276 }
277 if (UNLIKELY(MappedUser == 0))
278 Region->Data = Data;
279 if (UNLIKELY(!map(reinterpret_cast<void *>(RegionBeg + MappedUser),
280 UserMapSize, "scudo:primary",
281 MAP_ALLOWNOMEM | MAP_RESIZABLE, &Region->Data)))
282 return nullptr;
283 Region->MappedUser += UserMapSize;
284 C->getStats().add(StatMapped, UserMapSize);
285 }
286
287 const uptr NumberOfBlocks = Min(
288 8UL * MaxCount, (Region->MappedUser - Region->AllocatedUser) / Size);
289 DCHECK_GT(NumberOfBlocks, 0);
290
291 TransferBatch *B = nullptr;
292 constexpr uptr ShuffleArraySize = 48;
293 void *ShuffleArray[ShuffleArraySize];
294 u32 Count = 0;
295 const uptr P = RegionBeg + Region->AllocatedUser;
296 const uptr AllocatedUser = NumberOfBlocks * Size;
297 for (uptr I = P; I < P + AllocatedUser; I += Size) {
298 ShuffleArray[Count++] = reinterpret_cast<void *>(I);
299 if (Count == ShuffleArraySize) {
300 if (UNLIKELY(!populateBatches(C, Region, ClassId, &B, MaxCount,
301 ShuffleArray, Count)))
302 return nullptr;
303 Count = 0;
304 }
305 }
306 if (Count) {
307 if (UNLIKELY(!populateBatches(C, Region, ClassId, &B, MaxCount,
308 ShuffleArray, Count)))
309 return nullptr;
310 }
311 DCHECK(B);
312 DCHECK_GT(B->getCount(), 0);
313
314 C->getStats().add(StatFree, AllocatedUser);
315 Region->AllocatedUser += AllocatedUser;
316 Region->Exhausted = false;
317 if (Region->CanRelease)
318 Region->ReleaseInfo.LastReleaseAtNs = getMonotonicTime();
319
320 return B;
321 }
322
323 void printStats(uptr ClassId, uptr Rss) const {
324 RegionInfo *Region = getRegionInfo(ClassId);
325 if (Region->MappedUser == 0)
326 return;
327 const uptr InUse = Region->Stats.PoppedBlocks - Region->Stats.PushedBlocks;
328 const uptr AvailableChunks =
329 Region->AllocatedUser / getSizeByClassId(ClassId);
330 Printf("%s %02zu (%6zu): mapped: %6zuK popped: %7zu pushed: %7zu inuse: "
331 "%6zu avail: %6zu rss: %6zuK releases: %6zu last released: %6zuK "
332 "region: 0x%zx (0x%zx)\n",
333 Region->Exhausted ? "F" : " ", ClassId, getSizeByClassId(ClassId),
334 Region->MappedUser >> 10, Region->Stats.PoppedBlocks,
335 Region->Stats.PushedBlocks, InUse, AvailableChunks, Rss >> 10,
336 Region->ReleaseInfo.RangesReleased,
337 Region->ReleaseInfo.LastReleasedBytes >> 10, Region->RegionBeg,
338 getRegionBaseByClassId(ClassId));
339 }
340
341 NOINLINE void releaseToOSMaybe(RegionInfo *Region, uptr ClassId,
342 bool Force = false) {
343 const uptr BlockSize = getSizeByClassId(ClassId);
344 const uptr PageSize = getPageSizeCached();
345
346 CHECK_GE(Region->Stats.PoppedBlocks, Region->Stats.PushedBlocks);
347 const uptr N = Region->Stats.PoppedBlocks - Region->Stats.PushedBlocks;
348 if (N * BlockSize < PageSize)
349 return; // No chance to release anything.
350 if ((Region->Stats.PushedBlocks -
351 Region->ReleaseInfo.PushedBlocksAtLastRelease) *
352 BlockSize <
353 PageSize) {
354 return; // Nothing new to release.
355 }
356
357 if (!Force) {
358 const s32 IntervalMs = ReleaseToOsIntervalMs;
359 if (IntervalMs < 0)
360 return;
361 if (Region->ReleaseInfo.LastReleaseAtNs +
362 static_cast<uptr>(IntervalMs) * 1000000ULL >
363 getMonotonicTime()) {
364 return; // Memory was returned recently.
365 }
366 }
367
368 ReleaseRecorder Recorder(Region->RegionBeg, &Region->Data);
369 releaseFreeMemoryToOS(&Region->FreeList, Region->RegionBeg,
370 roundUpTo(Region->AllocatedUser, PageSize) / PageSize,
371 BlockSize, &Recorder);
372
373 if (Recorder.getReleasedRangesCount() > 0) {
374 Region->ReleaseInfo.PushedBlocksAtLastRelease =
375 Region->Stats.PushedBlocks;
376 Region->ReleaseInfo.RangesReleased += Recorder.getReleasedRangesCount();
377 Region->ReleaseInfo.LastReleasedBytes = Recorder.getReleasedBytes();
378 }
379 Region->ReleaseInfo.LastReleaseAtNs = getMonotonicTime();
380 }
381};
382
383} // namespace scudo
384
385#endif // SCUDO_PRIMARY64_H_