1 //===-- release.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_RELEASE_H_
10 #define SCUDO_RELEASE_H_
11
12 #include "common.h"
13 #include "list.h"
14 #include "mem_map.h"
15 #include "mutex.h"
16 #include "thread_annotations.h"
17
18 namespace scudo {
19
20 template <typename MemMapT> class RegionReleaseRecorder {
21 public:
22 RegionReleaseRecorder(MemMapT *RegionMemMap, uptr Base, uptr Offset = 0)
RegionMemMap(RegionMemMap)23 : RegionMemMap(RegionMemMap), Base(Base), Offset(Offset) {}
24
getReleasedRangesCount()25 uptr getReleasedRangesCount() const { return ReleasedRangesCount; }
26
getReleasedBytes()27 uptr getReleasedBytes() const { return ReleasedBytes; }
28
getBase()29 uptr getBase() const { return Base; }
30
31 // Releases [From, To) range of pages back to OS. Note that `From` and `To`
32 // are offseted from `Base` + Offset.
releasePageRangeToOS(uptr From,uptr To)33 void releasePageRangeToOS(uptr From, uptr To) {
34 const uptr Size = To - From;
35 RegionMemMap->releasePagesToOS(getBase() + Offset + From, Size);
36 ReleasedRangesCount++;
37 ReleasedBytes += Size;
38 }
39
40 private:
41 uptr ReleasedRangesCount = 0;
42 uptr ReleasedBytes = 0;
43 MemMapT *RegionMemMap = nullptr;
44 uptr Base = 0;
45 // The release offset from Base. This is used when we know a given range after
46 // Base will not be released.
47 uptr Offset = 0;
48 };
49
50 class ReleaseRecorder {
51 public:
52 ReleaseRecorder(uptr Base, uptr Offset = 0, MapPlatformData *Data = nullptr)
Base(Base)53 : Base(Base), Offset(Offset), Data(Data) {}
54
getReleasedRangesCount()55 uptr getReleasedRangesCount() const { return ReleasedRangesCount; }
56
getReleasedBytes()57 uptr getReleasedBytes() const { return ReleasedBytes; }
58
getBase()59 uptr getBase() const { return Base; }
60
61 // Releases [From, To) range of pages back to OS.
releasePageRangeToOS(uptr From,uptr To)62 void releasePageRangeToOS(uptr From, uptr To) {
63 const uptr Size = To - From;
64 releasePagesToOS(Base, From + Offset, Size, Data);
65 ReleasedRangesCount++;
66 ReleasedBytes += Size;
67 }
68
69 private:
70 uptr ReleasedRangesCount = 0;
71 uptr ReleasedBytes = 0;
72 // The starting address to release. Note that we may want to combine (Base +
73 // Offset) as a new Base. However, the Base is retrieved from
74 // `MapPlatformData` on Fuchsia, which means the offset won't be aware.
75 // Therefore, store them separately to make it work on all the platforms.
76 uptr Base = 0;
77 // The release offset from Base. This is used when we know a given range after
78 // Base will not be released.
79 uptr Offset = 0;
80 MapPlatformData *Data = nullptr;
81 };
82
83 // A buffer pool which holds a fixed number of static buffers for fast buffer
84 // allocation. If the request size is greater than `StaticBufferSize`, it'll
85 // delegate the allocation to map().
86 template <uptr StaticBufferCount, uptr StaticBufferSize> class BufferPool {
87 public:
88 // Preserve 1 bit in the `Mask` so that we don't need to do zero-check while
89 // extracting the least significant bit from the `Mask`.
90 static_assert(StaticBufferCount < SCUDO_WORDSIZE, "");
91 static_assert(isAligned(StaticBufferSize, SCUDO_CACHE_LINE_SIZE), "");
92
93 // Return a buffer which is at least `BufferSize`.
getBuffer(const uptr BufferSize)94 uptr *getBuffer(const uptr BufferSize) {
95 if (UNLIKELY(BufferSize > StaticBufferSize))
96 return getDynamicBuffer(BufferSize);
97
98 uptr index;
99 {
100 // TODO: In general, we expect this operation should be fast so the
101 // waiting thread won't be put into sleep. The HybridMutex does implement
102 // the busy-waiting but we may want to review the performance and see if
103 // we need an explict spin lock here.
104 ScopedLock L(Mutex);
105 index = getLeastSignificantSetBitIndex(Mask);
106 if (index < StaticBufferCount)
107 Mask ^= static_cast<uptr>(1) << index;
108 }
109
110 if (index >= StaticBufferCount)
111 return getDynamicBuffer(BufferSize);
112
113 const uptr Offset = index * StaticBufferSize;
114 memset(&RawBuffer[Offset], 0, StaticBufferSize);
115 return &RawBuffer[Offset];
116 }
117
releaseBuffer(uptr * Buffer,const uptr BufferSize)118 void releaseBuffer(uptr *Buffer, const uptr BufferSize) {
119 const uptr index = getStaticBufferIndex(Buffer, BufferSize);
120 if (index < StaticBufferCount) {
121 ScopedLock L(Mutex);
122 DCHECK_EQ((Mask & (static_cast<uptr>(1) << index)), 0U);
123 Mask |= static_cast<uptr>(1) << index;
124 } else {
125 unmap(reinterpret_cast<void *>(Buffer),
126 roundUp(BufferSize, getPageSizeCached()));
127 }
128 }
129
isStaticBufferTestOnly(uptr * Buffer,uptr BufferSize)130 bool isStaticBufferTestOnly(uptr *Buffer, uptr BufferSize) {
131 return getStaticBufferIndex(Buffer, BufferSize) < StaticBufferCount;
132 }
133
134 private:
getStaticBufferIndex(uptr * Buffer,uptr BufferSize)135 uptr getStaticBufferIndex(uptr *Buffer, uptr BufferSize) {
136 if (UNLIKELY(BufferSize > StaticBufferSize))
137 return StaticBufferCount;
138
139 const uptr BufferBase = reinterpret_cast<uptr>(Buffer);
140 const uptr RawBufferBase = reinterpret_cast<uptr>(RawBuffer);
141
142 if (BufferBase < RawBufferBase ||
143 BufferBase >= RawBufferBase + sizeof(RawBuffer)) {
144 return StaticBufferCount;
145 }
146
147 DCHECK_LE(BufferSize, StaticBufferSize);
148 DCHECK_LE(BufferBase + BufferSize, RawBufferBase + sizeof(RawBuffer));
149 DCHECK_EQ((BufferBase - RawBufferBase) % StaticBufferSize, 0U);
150
151 const uptr index =
152 (BufferBase - RawBufferBase) / (StaticBufferSize * sizeof(uptr));
153 DCHECK_LT(index, StaticBufferCount);
154 return index;
155 }
156
getDynamicBuffer(const uptr BufferSize)157 uptr *getDynamicBuffer(const uptr BufferSize) {
158 // When using a heap-based buffer, precommit the pages backing the
159 // Vmar by passing |MAP_PRECOMMIT| flag. This allows an optimization
160 // where page fault exceptions are skipped as the allocated memory
161 // is accessed. So far, this is only enabled on Fuchsia. It hasn't proven a
162 // performance benefit on other platforms.
163 const uptr MmapFlags = MAP_ALLOWNOMEM | (SCUDO_FUCHSIA ? MAP_PRECOMMIT : 0);
164 return reinterpret_cast<uptr *>(
165 map(nullptr, roundUp(BufferSize, getPageSizeCached()), "scudo:counters",
166 MmapFlags, &MapData));
167 }
168
169 HybridMutex Mutex;
170 // '1' means that buffer index is not used. '0' means the buffer is in use.
171 uptr Mask GUARDED_BY(Mutex) = ~static_cast<uptr>(0);
172 uptr RawBuffer[StaticBufferCount * StaticBufferSize] GUARDED_BY(Mutex);
173 [[no_unique_address]] MapPlatformData MapData = {};
174 };
175
176 // A Region page map is used to record the usage of pages in the regions. It
177 // implements a packed array of Counters. Each counter occupies 2^N bits, enough
178 // to store counter's MaxValue. Ctor will try to use a static buffer first, and
179 // if that fails (the buffer is too small or already locked), will allocate the
180 // required Buffer via map(). The caller is expected to check whether the
181 // initialization was successful by checking isAllocated() result. For
182 // performance sake, none of the accessors check the validity of the arguments,
183 // It is assumed that Index is always in [0, N) range and the value is not
184 // incremented past MaxValue.
185 class RegionPageMap {
186 public:
RegionPageMap()187 RegionPageMap()
188 : Regions(0),
189 NumCounters(0),
190 CounterSizeBitsLog(0),
191 CounterMask(0),
192 PackingRatioLog(0),
193 BitOffsetMask(0),
194 SizePerRegion(0),
195 BufferSize(0),
196 Buffer(nullptr) {}
RegionPageMap(uptr NumberOfRegions,uptr CountersPerRegion,uptr MaxValue)197 RegionPageMap(uptr NumberOfRegions, uptr CountersPerRegion, uptr MaxValue) {
198 reset(NumberOfRegions, CountersPerRegion, MaxValue);
199 }
~RegionPageMap()200 ~RegionPageMap() {
201 if (!isAllocated())
202 return;
203 Buffers.releaseBuffer(Buffer, BufferSize);
204 Buffer = nullptr;
205 }
206
207 // Lock of `StaticBuffer` is acquired conditionally and there's no easy way to
208 // specify the thread-safety attribute properly in current code structure.
209 // Besides, it's the only place we may want to check thread safety. Therefore,
210 // it's fine to bypass the thread-safety analysis now.
reset(uptr NumberOfRegion,uptr CountersPerRegion,uptr MaxValue)211 void reset(uptr NumberOfRegion, uptr CountersPerRegion, uptr MaxValue) {
212 DCHECK_GT(NumberOfRegion, 0);
213 DCHECK_GT(CountersPerRegion, 0);
214 DCHECK_GT(MaxValue, 0);
215
216 Regions = NumberOfRegion;
217 NumCounters = CountersPerRegion;
218
219 constexpr uptr MaxCounterBits = sizeof(*Buffer) * 8UL;
220 // Rounding counter storage size up to the power of two allows for using
221 // bit shifts calculating particular counter's Index and offset.
222 const uptr CounterSizeBits =
223 roundUpPowerOfTwo(getMostSignificantSetBitIndex(MaxValue) + 1);
224 DCHECK_LE(CounterSizeBits, MaxCounterBits);
225 CounterSizeBitsLog = getLog2(CounterSizeBits);
226 CounterMask = ~(static_cast<uptr>(0)) >> (MaxCounterBits - CounterSizeBits);
227
228 const uptr PackingRatio = MaxCounterBits >> CounterSizeBitsLog;
229 DCHECK_GT(PackingRatio, 0);
230 PackingRatioLog = getLog2(PackingRatio);
231 BitOffsetMask = PackingRatio - 1;
232
233 SizePerRegion =
234 roundUp(NumCounters, static_cast<uptr>(1U) << PackingRatioLog) >>
235 PackingRatioLog;
236 BufferSize = SizePerRegion * sizeof(*Buffer) * Regions;
237 Buffer = Buffers.getBuffer(BufferSize);
238 }
239
isAllocated()240 bool isAllocated() const { return !!Buffer; }
241
getCount()242 uptr getCount() const { return NumCounters; }
243
get(uptr Region,uptr I)244 uptr get(uptr Region, uptr I) const {
245 DCHECK_LT(Region, Regions);
246 DCHECK_LT(I, NumCounters);
247 const uptr Index = I >> PackingRatioLog;
248 const uptr BitOffset = (I & BitOffsetMask) << CounterSizeBitsLog;
249 return (Buffer[Region * SizePerRegion + Index] >> BitOffset) & CounterMask;
250 }
251
inc(uptr Region,uptr I)252 void inc(uptr Region, uptr I) const {
253 DCHECK_LT(get(Region, I), CounterMask);
254 const uptr Index = I >> PackingRatioLog;
255 const uptr BitOffset = (I & BitOffsetMask) << CounterSizeBitsLog;
256 DCHECK_LT(BitOffset, SCUDO_WORDSIZE);
257 DCHECK_EQ(isAllCounted(Region, I), false);
258 Buffer[Region * SizePerRegion + Index] += static_cast<uptr>(1U)
259 << BitOffset;
260 }
261
incN(uptr Region,uptr I,uptr N)262 void incN(uptr Region, uptr I, uptr N) const {
263 DCHECK_GT(N, 0U);
264 DCHECK_LE(N, CounterMask);
265 DCHECK_LE(get(Region, I), CounterMask - N);
266 const uptr Index = I >> PackingRatioLog;
267 const uptr BitOffset = (I & BitOffsetMask) << CounterSizeBitsLog;
268 DCHECK_LT(BitOffset, SCUDO_WORDSIZE);
269 DCHECK_EQ(isAllCounted(Region, I), false);
270 Buffer[Region * SizePerRegion + Index] += N << BitOffset;
271 }
272
incRange(uptr Region,uptr From,uptr To)273 void incRange(uptr Region, uptr From, uptr To) const {
274 DCHECK_LE(From, To);
275 const uptr Top = Min(To + 1, NumCounters);
276 for (uptr I = From; I < Top; I++)
277 inc(Region, I);
278 }
279
280 // Set the counter to the max value. Note that the max number of blocks in a
281 // page may vary. To provide an easier way to tell if all the blocks are
282 // counted for different pages, set to the same max value to denote the
283 // all-counted status.
setAsAllCounted(uptr Region,uptr I)284 void setAsAllCounted(uptr Region, uptr I) const {
285 DCHECK_LE(get(Region, I), CounterMask);
286 const uptr Index = I >> PackingRatioLog;
287 const uptr BitOffset = (I & BitOffsetMask) << CounterSizeBitsLog;
288 DCHECK_LT(BitOffset, SCUDO_WORDSIZE);
289 Buffer[Region * SizePerRegion + Index] |= CounterMask << BitOffset;
290 }
setAsAllCountedRange(uptr Region,uptr From,uptr To)291 void setAsAllCountedRange(uptr Region, uptr From, uptr To) const {
292 DCHECK_LE(From, To);
293 const uptr Top = Min(To + 1, NumCounters);
294 for (uptr I = From; I < Top; I++)
295 setAsAllCounted(Region, I);
296 }
297
updateAsAllCountedIf(uptr Region,uptr I,uptr MaxCount)298 bool updateAsAllCountedIf(uptr Region, uptr I, uptr MaxCount) {
299 const uptr Count = get(Region, I);
300 if (Count == CounterMask)
301 return true;
302 if (Count == MaxCount) {
303 setAsAllCounted(Region, I);
304 return true;
305 }
306 return false;
307 }
isAllCounted(uptr Region,uptr I)308 bool isAllCounted(uptr Region, uptr I) const {
309 return get(Region, I) == CounterMask;
310 }
311
getBufferSize()312 uptr getBufferSize() const { return BufferSize; }
313
314 private:
315 uptr Regions;
316 uptr NumCounters;
317 uptr CounterSizeBitsLog;
318 uptr CounterMask;
319 uptr PackingRatioLog;
320 uptr BitOffsetMask;
321
322 uptr SizePerRegion;
323 uptr BufferSize;
324 uptr *Buffer;
325
326 // We may consider making this configurable if there are cases which may
327 // benefit from this.
328 static const uptr StaticBufferCount = 2U;
329 static const uptr StaticBufferSize = 512U;
330 static BufferPool<StaticBufferCount, StaticBufferSize> Buffers;
331 };
332
333 template <class ReleaseRecorderT> class FreePagesRangeTracker {
334 public:
FreePagesRangeTracker(ReleaseRecorderT & Recorder)335 explicit FreePagesRangeTracker(ReleaseRecorderT &Recorder)
336 : Recorder(Recorder), PageSizeLog(getLog2(getPageSizeCached())) {}
337
processNextPage(bool Released)338 void processNextPage(bool Released) {
339 if (Released) {
340 if (!InRange) {
341 CurrentRangeStatePage = CurrentPage;
342 InRange = true;
343 }
344 } else {
345 closeOpenedRange();
346 }
347 CurrentPage++;
348 }
349
skipPages(uptr N)350 void skipPages(uptr N) {
351 closeOpenedRange();
352 CurrentPage += N;
353 }
354
finish()355 void finish() { closeOpenedRange(); }
356
357 private:
closeOpenedRange()358 void closeOpenedRange() {
359 if (InRange) {
360 Recorder.releasePageRangeToOS((CurrentRangeStatePage << PageSizeLog),
361 (CurrentPage << PageSizeLog));
362 InRange = false;
363 }
364 }
365
366 ReleaseRecorderT &Recorder;
367 const uptr PageSizeLog;
368 bool InRange = false;
369 uptr CurrentPage = 0;
370 uptr CurrentRangeStatePage = 0;
371 };
372
373 struct PageReleaseContext {
374 PageReleaseContext(uptr BlockSize, uptr NumberOfRegions, uptr ReleaseSize,
375 uptr ReleaseOffset = 0)
BlockSizePageReleaseContext376 : BlockSize(BlockSize), NumberOfRegions(NumberOfRegions) {
377 PageSize = getPageSizeCached();
378 if (BlockSize <= PageSize) {
379 if (PageSize % BlockSize == 0) {
380 // Same number of chunks per page, no cross overs.
381 FullPagesBlockCountMax = PageSize / BlockSize;
382 SameBlockCountPerPage = true;
383 } else if (BlockSize % (PageSize % BlockSize) == 0) {
384 // Some chunks are crossing page boundaries, which means that the page
385 // contains one or two partial chunks, but all pages contain the same
386 // number of chunks.
387 FullPagesBlockCountMax = PageSize / BlockSize + 1;
388 SameBlockCountPerPage = true;
389 } else {
390 // Some chunks are crossing page boundaries, which means that the page
391 // contains one or two partial chunks.
392 FullPagesBlockCountMax = PageSize / BlockSize + 2;
393 SameBlockCountPerPage = false;
394 }
395 } else {
396 if (BlockSize % PageSize == 0) {
397 // One chunk covers multiple pages, no cross overs.
398 FullPagesBlockCountMax = 1;
399 SameBlockCountPerPage = true;
400 } else {
401 // One chunk covers multiple pages, Some chunks are crossing page
402 // boundaries. Some pages contain one chunk, some contain two.
403 FullPagesBlockCountMax = 2;
404 SameBlockCountPerPage = false;
405 }
406 }
407
408 // TODO: For multiple regions, it's more complicated to support partial
409 // region marking (which includes the complexity of how to handle the last
410 // block in a region). We may consider this after markFreeBlocks() accepts
411 // only free blocks from the same region.
412 if (NumberOfRegions != 1)
413 DCHECK_EQ(ReleaseOffset, 0U);
414
415 PagesCount = roundUp(ReleaseSize, PageSize) / PageSize;
416 PageSizeLog = getLog2(PageSize);
417 ReleasePageOffset = ReleaseOffset >> PageSizeLog;
418 }
419
420 // PageMap is lazily allocated when markFreeBlocks() is invoked.
hasBlockMarkedPageReleaseContext421 bool hasBlockMarked() const {
422 return PageMap.isAllocated();
423 }
424
ensurePageMapAllocatedPageReleaseContext425 bool ensurePageMapAllocated() {
426 if (PageMap.isAllocated())
427 return true;
428 PageMap.reset(NumberOfRegions, PagesCount, FullPagesBlockCountMax);
429 // TODO: Log some message when we fail on PageMap allocation.
430 return PageMap.isAllocated();
431 }
432
433 // Mark all the blocks in the given range [From, to). Instead of visiting all
434 // the blocks, we will just mark the page as all counted. Note the `From` and
435 // `To` has to be page aligned but with one exception, if `To` is equal to the
436 // RegionSize, it's not necessary to be aligned with page size.
markRangeAsAllCountedPageReleaseContext437 bool markRangeAsAllCounted(uptr From, uptr To, uptr Base,
438 const uptr RegionIndex, const uptr RegionSize) {
439 DCHECK_LT(From, To);
440 DCHECK_LE(To, Base + RegionSize);
441 DCHECK_EQ(From % PageSize, 0U);
442 DCHECK_LE(To - From, RegionSize);
443
444 if (!ensurePageMapAllocated())
445 return false;
446
447 uptr FromInRegion = From - Base;
448 uptr ToInRegion = To - Base;
449 uptr FirstBlockInRange = roundUpSlow(FromInRegion, BlockSize);
450
451 // The straddling block sits across entire range.
452 if (FirstBlockInRange >= ToInRegion)
453 return true;
454
455 // First block may not sit at the first pape in the range, move
456 // `FromInRegion` to the first block page.
457 FromInRegion = roundDown(FirstBlockInRange, PageSize);
458
459 // When The first block is not aligned to the range boundary, which means
460 // there is a block sitting acorss `From`, that looks like,
461 //
462 // From To
463 // V V
464 // +-----------------------------------------------+
465 // +-----+-----+-----+-----+
466 // | | | | | ...
467 // +-----+-----+-----+-----+
468 // |- first page -||- second page -||- ...
469 //
470 // Therefore, we can't just mark the first page as all counted. Instead, we
471 // increment the number of blocks in the first page in the page map and
472 // then round up the `From` to the next page.
473 if (FirstBlockInRange != FromInRegion) {
474 DCHECK_GT(FromInRegion + PageSize, FirstBlockInRange);
475 uptr NumBlocksInFirstPage =
476 (FromInRegion + PageSize - FirstBlockInRange + BlockSize - 1) /
477 BlockSize;
478 PageMap.incN(RegionIndex, getPageIndex(FromInRegion),
479 NumBlocksInFirstPage);
480 FromInRegion = roundUp(FromInRegion + 1, PageSize);
481 }
482
483 uptr LastBlockInRange = roundDownSlow(ToInRegion - 1, BlockSize);
484
485 // Note that LastBlockInRange may be smaller than `FromInRegion` at this
486 // point because it may contain only one block in the range.
487
488 // When the last block sits across `To`, we can't just mark the pages
489 // occupied by the last block as all counted. Instead, we increment the
490 // counters of those pages by 1. The exception is that if it's the last
491 // block in the region, it's fine to mark those pages as all counted.
492 if (LastBlockInRange + BlockSize != RegionSize) {
493 DCHECK_EQ(ToInRegion % PageSize, 0U);
494 // The case below is like,
495 //
496 // From To
497 // V V
498 // +----------------------------------------+
499 // +-----+-----+-----+-----+
500 // | | | | | ...
501 // +-----+-----+-----+-----+
502 // ... -||- last page -||- next page -|
503 //
504 // The last block is not aligned to `To`, we need to increment the
505 // counter of `next page` by 1.
506 if (LastBlockInRange + BlockSize != ToInRegion) {
507 PageMap.incRange(RegionIndex, getPageIndex(ToInRegion),
508 getPageIndex(LastBlockInRange + BlockSize - 1));
509 }
510 } else {
511 ToInRegion = RegionSize;
512 }
513
514 // After handling the first page and the last block, it's safe to mark any
515 // page in between the range [From, To).
516 if (FromInRegion < ToInRegion) {
517 PageMap.setAsAllCountedRange(RegionIndex, getPageIndex(FromInRegion),
518 getPageIndex(ToInRegion - 1));
519 }
520
521 return true;
522 }
523
524 template <class TransferBatchT, typename DecompactPtrT>
markFreeBlocksInRegionPageReleaseContext525 bool markFreeBlocksInRegion(const IntrusiveList<TransferBatchT> &FreeList,
526 DecompactPtrT DecompactPtr, const uptr Base,
527 const uptr RegionIndex, const uptr RegionSize,
528 bool MayContainLastBlockInRegion) {
529 if (!ensurePageMapAllocated())
530 return false;
531
532 if (MayContainLastBlockInRegion) {
533 const uptr LastBlockInRegion =
534 ((RegionSize / BlockSize) - 1U) * BlockSize;
535 // The last block in a region may not use the entire page, we mark the
536 // following "pretend" memory block(s) as free in advance.
537 //
538 // Region Boundary
539 // v
540 // -----+-----------------------+
541 // | Last Page | <- Rounded Region Boundary
542 // -----+-----------------------+
543 // |-----||- trailing blocks -|
544 // ^
545 // last block
546 const uptr RoundedRegionSize = roundUp(RegionSize, PageSize);
547 const uptr TrailingBlockBase = LastBlockInRegion + BlockSize;
548 // If the difference between `RoundedRegionSize` and
549 // `TrailingBlockBase` is larger than a page, that implies the reported
550 // `RegionSize` may not be accurate.
551 DCHECK_LT(RoundedRegionSize - TrailingBlockBase, PageSize);
552
553 // Only the last page touched by the last block needs to mark the trailing
554 // blocks. Note that if the last "pretend" block straddles the boundary,
555 // we still have to count it in so that the logic of counting the number
556 // of blocks on a page is consistent.
557 uptr NumTrailingBlocks =
558 (roundUpSlow(RoundedRegionSize - TrailingBlockBase, BlockSize) +
559 BlockSize - 1) /
560 BlockSize;
561 if (NumTrailingBlocks > 0) {
562 PageMap.incN(RegionIndex, getPageIndex(TrailingBlockBase),
563 NumTrailingBlocks);
564 }
565 }
566
567 // Iterate over free chunks and count how many free chunks affect each
568 // allocated page.
569 if (BlockSize <= PageSize && PageSize % BlockSize == 0) {
570 // Each chunk affects one page only.
571 for (const auto &It : FreeList) {
572 for (u16 I = 0; I < It.getCount(); I++) {
573 const uptr PInRegion = DecompactPtr(It.get(I)) - Base;
574 DCHECK_LT(PInRegion, RegionSize);
575 PageMap.inc(RegionIndex, getPageIndex(PInRegion));
576 }
577 }
578 } else {
579 // In all other cases chunks might affect more than one page.
580 DCHECK_GE(RegionSize, BlockSize);
581 for (const auto &It : FreeList) {
582 for (u16 I = 0; I < It.getCount(); I++) {
583 const uptr PInRegion = DecompactPtr(It.get(I)) - Base;
584 PageMap.incRange(RegionIndex, getPageIndex(PInRegion),
585 getPageIndex(PInRegion + BlockSize - 1));
586 }
587 }
588 }
589
590 return true;
591 }
592
getPageIndexPageReleaseContext593 uptr getPageIndex(uptr P) { return (P >> PageSizeLog) - ReleasePageOffset; }
594
595 uptr BlockSize;
596 uptr NumberOfRegions;
597 // For partial region marking, some pages in front are not needed to be
598 // counted.
599 uptr ReleasePageOffset;
600 uptr PageSize;
601 uptr PagesCount;
602 uptr PageSizeLog;
603 uptr FullPagesBlockCountMax;
604 bool SameBlockCountPerPage;
605 RegionPageMap PageMap;
606 };
607
608 // Try to release the page which doesn't have any in-used block, i.e., they are
609 // all free blocks. The `PageMap` will record the number of free blocks in each
610 // page.
611 template <class ReleaseRecorderT, typename SkipRegionT>
612 NOINLINE void
releaseFreeMemoryToOS(PageReleaseContext & Context,ReleaseRecorderT & Recorder,SkipRegionT SkipRegion)613 releaseFreeMemoryToOS(PageReleaseContext &Context,
614 ReleaseRecorderT &Recorder, SkipRegionT SkipRegion) {
615 const uptr PageSize = Context.PageSize;
616 const uptr BlockSize = Context.BlockSize;
617 const uptr PagesCount = Context.PagesCount;
618 const uptr NumberOfRegions = Context.NumberOfRegions;
619 const uptr ReleasePageOffset = Context.ReleasePageOffset;
620 const uptr FullPagesBlockCountMax = Context.FullPagesBlockCountMax;
621 const bool SameBlockCountPerPage = Context.SameBlockCountPerPage;
622 RegionPageMap &PageMap = Context.PageMap;
623
624 // Iterate over pages detecting ranges of pages with chunk Counters equal
625 // to the expected number of chunks for the particular page.
626 FreePagesRangeTracker<ReleaseRecorderT> RangeTracker(Recorder);
627 if (SameBlockCountPerPage) {
628 // Fast path, every page has the same number of chunks affecting it.
629 for (uptr I = 0; I < NumberOfRegions; I++) {
630 if (SkipRegion(I)) {
631 RangeTracker.skipPages(PagesCount);
632 continue;
633 }
634 for (uptr J = 0; J < PagesCount; J++) {
635 const bool CanRelease =
636 PageMap.updateAsAllCountedIf(I, J, FullPagesBlockCountMax);
637 RangeTracker.processNextPage(CanRelease);
638 }
639 }
640 } else {
641 // Slow path, go through the pages keeping count how many chunks affect
642 // each page.
643 const uptr Pn = BlockSize < PageSize ? PageSize / BlockSize : 1;
644 const uptr Pnc = Pn * BlockSize;
645 // The idea is to increment the current page pointer by the first chunk
646 // size, middle portion size (the portion of the page covered by chunks
647 // except the first and the last one) and then the last chunk size, adding
648 // up the number of chunks on the current page and checking on every step
649 // whether the page boundary was crossed.
650 for (uptr I = 0; I < NumberOfRegions; I++) {
651 if (SkipRegion(I)) {
652 RangeTracker.skipPages(PagesCount);
653 continue;
654 }
655 uptr PrevPageBoundary = 0;
656 uptr CurrentBoundary = 0;
657 if (ReleasePageOffset > 0) {
658 PrevPageBoundary = ReleasePageOffset * PageSize;
659 CurrentBoundary = roundUpSlow(PrevPageBoundary, BlockSize);
660 }
661 for (uptr J = 0; J < PagesCount; J++) {
662 const uptr PageBoundary = PrevPageBoundary + PageSize;
663 uptr BlocksPerPage = Pn;
664 if (CurrentBoundary < PageBoundary) {
665 if (CurrentBoundary > PrevPageBoundary)
666 BlocksPerPage++;
667 CurrentBoundary += Pnc;
668 if (CurrentBoundary < PageBoundary) {
669 BlocksPerPage++;
670 CurrentBoundary += BlockSize;
671 }
672 }
673 PrevPageBoundary = PageBoundary;
674 const bool CanRelease =
675 PageMap.updateAsAllCountedIf(I, J, BlocksPerPage);
676 RangeTracker.processNextPage(CanRelease);
677 }
678 }
679 }
680 RangeTracker.finish();
681 }
682
683 } // namespace scudo
684
685 #endif // SCUDO_RELEASE_H_
686