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