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