13cab2bb3Spatrick //===-- release.h -----------------------------------------------*- C++ -*-===//
23cab2bb3Spatrick //
33cab2bb3Spatrick // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
43cab2bb3Spatrick // See https://llvm.org/LICENSE.txt for license information.
53cab2bb3Spatrick // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
63cab2bb3Spatrick //
73cab2bb3Spatrick //===----------------------------------------------------------------------===//
83cab2bb3Spatrick 
93cab2bb3Spatrick #ifndef SCUDO_RELEASE_H_
103cab2bb3Spatrick #define SCUDO_RELEASE_H_
113cab2bb3Spatrick 
123cab2bb3Spatrick #include "common.h"
133cab2bb3Spatrick #include "list.h"
141f9cb04fSpatrick #include "mutex.h"
153cab2bb3Spatrick 
163cab2bb3Spatrick namespace scudo {
173cab2bb3Spatrick 
183cab2bb3Spatrick class ReleaseRecorder {
193cab2bb3Spatrick public:
20d89ec533Spatrick   ReleaseRecorder(uptr Base, MapPlatformData *Data = nullptr)
Base(Base)21d89ec533Spatrick       : Base(Base), Data(Data) {}
223cab2bb3Spatrick 
getReleasedRangesCount()233cab2bb3Spatrick   uptr getReleasedRangesCount() const { return ReleasedRangesCount; }
243cab2bb3Spatrick 
getReleasedBytes()253cab2bb3Spatrick   uptr getReleasedBytes() const { return ReleasedBytes; }
263cab2bb3Spatrick 
getBase()27d89ec533Spatrick   uptr getBase() const { return Base; }
28d89ec533Spatrick 
293cab2bb3Spatrick   // Releases [From, To) range of pages back to OS.
releasePageRangeToOS(uptr From,uptr To)303cab2bb3Spatrick   void releasePageRangeToOS(uptr From, uptr To) {
313cab2bb3Spatrick     const uptr Size = To - From;
32d89ec533Spatrick     releasePagesToOS(Base, From, Size, Data);
333cab2bb3Spatrick     ReleasedRangesCount++;
343cab2bb3Spatrick     ReleasedBytes += Size;
353cab2bb3Spatrick   }
363cab2bb3Spatrick 
373cab2bb3Spatrick private:
383cab2bb3Spatrick   uptr ReleasedRangesCount = 0;
393cab2bb3Spatrick   uptr ReleasedBytes = 0;
40d89ec533Spatrick   uptr Base = 0;
413cab2bb3Spatrick   MapPlatformData *Data = nullptr;
423cab2bb3Spatrick };
433cab2bb3Spatrick 
44*810390e3Srobert // A Region page map is used to record the usage of pages in the regions. It
45*810390e3Srobert // implements a packed array of Counters. Each counter occupies 2^N bits, enough
46*810390e3Srobert // to store counter's MaxValue. Ctor will try to use a static buffer first, and
47*810390e3Srobert // if that fails (the buffer is too small or already locked), will allocate the
481f9cb04fSpatrick // required Buffer via map(). The caller is expected to check whether the
491f9cb04fSpatrick // initialization was successful by checking isAllocated() result. For
501f9cb04fSpatrick // performance sake, none of the accessors check the validity of the arguments,
511f9cb04fSpatrick // It is assumed that Index is always in [0, N) range and the value is not
521f9cb04fSpatrick // incremented past MaxValue.
53*810390e3Srobert class RegionPageMap {
543cab2bb3Spatrick public:
RegionPageMap()55*810390e3Srobert   RegionPageMap()
56*810390e3Srobert       : Regions(0),
57*810390e3Srobert         NumCounters(0),
58*810390e3Srobert         CounterSizeBitsLog(0),
59*810390e3Srobert         CounterMask(0),
60*810390e3Srobert         PackingRatioLog(0),
61*810390e3Srobert         BitOffsetMask(0),
62*810390e3Srobert         SizePerRegion(0),
63*810390e3Srobert         BufferSize(0),
64*810390e3Srobert         Buffer(nullptr) {}
RegionPageMap(uptr NumberOfRegions,uptr CountersPerRegion,uptr MaxValue)65*810390e3Srobert   RegionPageMap(uptr NumberOfRegions, uptr CountersPerRegion, uptr MaxValue) {
66*810390e3Srobert     reset(NumberOfRegions, CountersPerRegion, MaxValue);
67*810390e3Srobert   }
~RegionPageMap()68*810390e3Srobert   ~RegionPageMap() {
69*810390e3Srobert     if (!isAllocated())
70*810390e3Srobert       return;
71*810390e3Srobert     if (Buffer == &StaticBuffer[0])
72*810390e3Srobert       Mutex.unlock();
73*810390e3Srobert     else
74*810390e3Srobert       unmap(reinterpret_cast<void *>(Buffer),
75*810390e3Srobert             roundUpTo(BufferSize, getPageSizeCached()));
76*810390e3Srobert     Buffer = nullptr;
77*810390e3Srobert   }
78*810390e3Srobert 
reset(uptr NumberOfRegion,uptr CountersPerRegion,uptr MaxValue)79*810390e3Srobert   void reset(uptr NumberOfRegion, uptr CountersPerRegion, uptr MaxValue) {
80*810390e3Srobert     DCHECK_GT(NumberOfRegion, 0);
81*810390e3Srobert     DCHECK_GT(CountersPerRegion, 0);
82d89ec533Spatrick     DCHECK_GT(MaxValue, 0);
83*810390e3Srobert 
84*810390e3Srobert     Regions = NumberOfRegion;
85*810390e3Srobert     NumCounters = CountersPerRegion;
86*810390e3Srobert 
873cab2bb3Spatrick     constexpr uptr MaxCounterBits = sizeof(*Buffer) * 8UL;
883cab2bb3Spatrick     // Rounding counter storage size up to the power of two allows for using
893cab2bb3Spatrick     // bit shifts calculating particular counter's Index and offset.
903cab2bb3Spatrick     const uptr CounterSizeBits =
913cab2bb3Spatrick         roundUpToPowerOfTwo(getMostSignificantSetBitIndex(MaxValue) + 1);
92d89ec533Spatrick     DCHECK_LE(CounterSizeBits, MaxCounterBits);
933cab2bb3Spatrick     CounterSizeBitsLog = getLog2(CounterSizeBits);
943cab2bb3Spatrick     CounterMask = ~(static_cast<uptr>(0)) >> (MaxCounterBits - CounterSizeBits);
953cab2bb3Spatrick 
963cab2bb3Spatrick     const uptr PackingRatio = MaxCounterBits >> CounterSizeBitsLog;
97d89ec533Spatrick     DCHECK_GT(PackingRatio, 0);
983cab2bb3Spatrick     PackingRatioLog = getLog2(PackingRatio);
993cab2bb3Spatrick     BitOffsetMask = PackingRatio - 1;
1003cab2bb3Spatrick 
101d89ec533Spatrick     SizePerRegion =
102d89ec533Spatrick         roundUpTo(NumCounters, static_cast<uptr>(1U) << PackingRatioLog) >>
103d89ec533Spatrick         PackingRatioLog;
104d89ec533Spatrick     BufferSize = SizePerRegion * sizeof(*Buffer) * Regions;
105d89ec533Spatrick     if (BufferSize <= (StaticBufferCount * sizeof(Buffer[0])) &&
106d89ec533Spatrick         Mutex.tryLock()) {
1071f9cb04fSpatrick       Buffer = &StaticBuffer[0];
1081f9cb04fSpatrick       memset(Buffer, 0, BufferSize);
1091f9cb04fSpatrick     } else {
110*810390e3Srobert       // When using a heap-based buffer, precommit the pages backing the
111*810390e3Srobert       // Vmar by passing |MAP_PRECOMMIT| flag. This allows an optimization
112*810390e3Srobert       // where page fault exceptions are skipped as the allocated memory
113*810390e3Srobert       // is accessed.
114*810390e3Srobert       const uptr MmapFlags =
115*810390e3Srobert           MAP_ALLOWNOMEM | (SCUDO_FUCHSIA ? MAP_PRECOMMIT : 0);
1163cab2bb3Spatrick       Buffer = reinterpret_cast<uptr *>(
117d89ec533Spatrick           map(nullptr, roundUpTo(BufferSize, getPageSizeCached()),
118*810390e3Srobert               "scudo:counters", MmapFlags, &MapData));
1193cab2bb3Spatrick     }
1201f9cb04fSpatrick   }
1213cab2bb3Spatrick 
isAllocated()1223cab2bb3Spatrick   bool isAllocated() const { return !!Buffer; }
1233cab2bb3Spatrick 
getCount()124d89ec533Spatrick   uptr getCount() const { return NumCounters; }
1253cab2bb3Spatrick 
get(uptr Region,uptr I)126d89ec533Spatrick   uptr get(uptr Region, uptr I) const {
127d89ec533Spatrick     DCHECK_LT(Region, Regions);
128d89ec533Spatrick     DCHECK_LT(I, NumCounters);
1293cab2bb3Spatrick     const uptr Index = I >> PackingRatioLog;
1303cab2bb3Spatrick     const uptr BitOffset = (I & BitOffsetMask) << CounterSizeBitsLog;
131d89ec533Spatrick     return (Buffer[Region * SizePerRegion + Index] >> BitOffset) & CounterMask;
1323cab2bb3Spatrick   }
1333cab2bb3Spatrick 
inc(uptr Region,uptr I)134d89ec533Spatrick   void inc(uptr Region, uptr I) const {
135d89ec533Spatrick     DCHECK_LT(get(Region, I), CounterMask);
1363cab2bb3Spatrick     const uptr Index = I >> PackingRatioLog;
1373cab2bb3Spatrick     const uptr BitOffset = (I & BitOffsetMask) << CounterSizeBitsLog;
1383cab2bb3Spatrick     DCHECK_LT(BitOffset, SCUDO_WORDSIZE);
139*810390e3Srobert     DCHECK_EQ(isAllCounted(Region, I), false);
140d89ec533Spatrick     Buffer[Region * SizePerRegion + Index] += static_cast<uptr>(1U)
141d89ec533Spatrick                                               << BitOffset;
1423cab2bb3Spatrick   }
1433cab2bb3Spatrick 
incRange(uptr Region,uptr From,uptr To)144d89ec533Spatrick   void incRange(uptr Region, uptr From, uptr To) const {
1453cab2bb3Spatrick     DCHECK_LE(From, To);
146d89ec533Spatrick     const uptr Top = Min(To + 1, NumCounters);
1471f9cb04fSpatrick     for (uptr I = From; I < Top; I++)
148d89ec533Spatrick       inc(Region, I);
1493cab2bb3Spatrick   }
1503cab2bb3Spatrick 
151*810390e3Srobert   // Set the counter to the max value. Note that the max number of blocks in a
152*810390e3Srobert   // page may vary. To provide an easier way to tell if all the blocks are
153*810390e3Srobert   // counted for different pages, set to the same max value to denote the
154*810390e3Srobert   // all-counted status.
setAsAllCounted(uptr Region,uptr I)155*810390e3Srobert   void setAsAllCounted(uptr Region, uptr I) const {
156*810390e3Srobert     DCHECK_LE(get(Region, I), CounterMask);
157*810390e3Srobert     const uptr Index = I >> PackingRatioLog;
158*810390e3Srobert     const uptr BitOffset = (I & BitOffsetMask) << CounterSizeBitsLog;
159*810390e3Srobert     DCHECK_LT(BitOffset, SCUDO_WORDSIZE);
160*810390e3Srobert     Buffer[Region * SizePerRegion + Index] |= CounterMask << BitOffset;
161*810390e3Srobert   }
isAllCounted(uptr Region,uptr I)162*810390e3Srobert   bool isAllCounted(uptr Region, uptr I) const {
163*810390e3Srobert     return get(Region, I) == CounterMask;
164*810390e3Srobert   }
165*810390e3Srobert 
getBufferSize()1663cab2bb3Spatrick   uptr getBufferSize() const { return BufferSize; }
1673cab2bb3Spatrick 
168d89ec533Spatrick   static const uptr StaticBufferCount = 2048U;
169d89ec533Spatrick 
1703cab2bb3Spatrick private:
171*810390e3Srobert   uptr Regions;
172*810390e3Srobert   uptr NumCounters;
1733cab2bb3Spatrick   uptr CounterSizeBitsLog;
1743cab2bb3Spatrick   uptr CounterMask;
1753cab2bb3Spatrick   uptr PackingRatioLog;
1763cab2bb3Spatrick   uptr BitOffsetMask;
1773cab2bb3Spatrick 
178d89ec533Spatrick   uptr SizePerRegion;
1793cab2bb3Spatrick   uptr BufferSize;
1803cab2bb3Spatrick   uptr *Buffer;
181*810390e3Srobert   [[no_unique_address]] MapPlatformData MapData = {};
1821f9cb04fSpatrick 
1831f9cb04fSpatrick   static HybridMutex Mutex;
184d89ec533Spatrick   static uptr StaticBuffer[StaticBufferCount];
1853cab2bb3Spatrick };
1863cab2bb3Spatrick 
1873cab2bb3Spatrick template <class ReleaseRecorderT> class FreePagesRangeTracker {
1883cab2bb3Spatrick public:
FreePagesRangeTracker(ReleaseRecorderT & Recorder)189*810390e3Srobert   explicit FreePagesRangeTracker(ReleaseRecorderT &Recorder)
1903cab2bb3Spatrick       : Recorder(Recorder), PageSizeLog(getLog2(getPageSizeCached())) {}
1913cab2bb3Spatrick 
processNextPage(bool Released)192*810390e3Srobert   void processNextPage(bool Released) {
193*810390e3Srobert     if (Released) {
1943cab2bb3Spatrick       if (!InRange) {
1953cab2bb3Spatrick         CurrentRangeStatePage = CurrentPage;
1963cab2bb3Spatrick         InRange = true;
1973cab2bb3Spatrick       }
1983cab2bb3Spatrick     } else {
1993cab2bb3Spatrick       closeOpenedRange();
2003cab2bb3Spatrick     }
2013cab2bb3Spatrick     CurrentPage++;
2023cab2bb3Spatrick   }
2033cab2bb3Spatrick 
skipPages(uptr N)204d89ec533Spatrick   void skipPages(uptr N) {
205d89ec533Spatrick     closeOpenedRange();
206d89ec533Spatrick     CurrentPage += N;
207d89ec533Spatrick   }
208d89ec533Spatrick 
finish()2093cab2bb3Spatrick   void finish() { closeOpenedRange(); }
2103cab2bb3Spatrick 
2113cab2bb3Spatrick private:
closeOpenedRange()2123cab2bb3Spatrick   void closeOpenedRange() {
2133cab2bb3Spatrick     if (InRange) {
214*810390e3Srobert       Recorder.releasePageRangeToOS((CurrentRangeStatePage << PageSizeLog),
2153cab2bb3Spatrick                                     (CurrentPage << PageSizeLog));
2163cab2bb3Spatrick       InRange = false;
2173cab2bb3Spatrick     }
2183cab2bb3Spatrick   }
2193cab2bb3Spatrick 
220*810390e3Srobert   ReleaseRecorderT &Recorder;
2213cab2bb3Spatrick   const uptr PageSizeLog;
2223cab2bb3Spatrick   bool InRange = false;
2233cab2bb3Spatrick   uptr CurrentPage = 0;
2243cab2bb3Spatrick   uptr CurrentRangeStatePage = 0;
2253cab2bb3Spatrick };
2263cab2bb3Spatrick 
227*810390e3Srobert struct PageReleaseContext {
PageReleaseContextPageReleaseContext228*810390e3Srobert   PageReleaseContext(uptr BlockSize, uptr RegionSize, uptr NumberOfRegions) :
229*810390e3Srobert       BlockSize(BlockSize),
230*810390e3Srobert       RegionSize(RegionSize),
231*810390e3Srobert       NumberOfRegions(NumberOfRegions) {
232*810390e3Srobert     PageSize = getPageSizeCached();
2333cab2bb3Spatrick     if (BlockSize <= PageSize) {
2343cab2bb3Spatrick       if (PageSize % BlockSize == 0) {
2353cab2bb3Spatrick         // Same number of chunks per page, no cross overs.
2363cab2bb3Spatrick         FullPagesBlockCountMax = PageSize / BlockSize;
2373cab2bb3Spatrick         SameBlockCountPerPage = true;
2383cab2bb3Spatrick       } else if (BlockSize % (PageSize % BlockSize) == 0) {
2393cab2bb3Spatrick         // Some chunks are crossing page boundaries, which means that the page
2403cab2bb3Spatrick         // contains one or two partial chunks, but all pages contain the same
2413cab2bb3Spatrick         // number of chunks.
2423cab2bb3Spatrick         FullPagesBlockCountMax = PageSize / BlockSize + 1;
2433cab2bb3Spatrick         SameBlockCountPerPage = true;
2443cab2bb3Spatrick       } else {
2453cab2bb3Spatrick         // Some chunks are crossing page boundaries, which means that the page
2463cab2bb3Spatrick         // contains one or two partial chunks.
2473cab2bb3Spatrick         FullPagesBlockCountMax = PageSize / BlockSize + 2;
2483cab2bb3Spatrick         SameBlockCountPerPage = false;
2493cab2bb3Spatrick       }
2503cab2bb3Spatrick     } else {
2513cab2bb3Spatrick       if (BlockSize % PageSize == 0) {
2523cab2bb3Spatrick         // One chunk covers multiple pages, no cross overs.
2533cab2bb3Spatrick         FullPagesBlockCountMax = 1;
2543cab2bb3Spatrick         SameBlockCountPerPage = true;
2553cab2bb3Spatrick       } else {
2563cab2bb3Spatrick         // One chunk covers multiple pages, Some chunks are crossing page
2573cab2bb3Spatrick         // boundaries. Some pages contain one chunk, some contain two.
2583cab2bb3Spatrick         FullPagesBlockCountMax = 2;
2593cab2bb3Spatrick         SameBlockCountPerPage = false;
2603cab2bb3Spatrick       }
2613cab2bb3Spatrick     }
2623cab2bb3Spatrick 
263*810390e3Srobert     PagesCount = roundUpTo(RegionSize, PageSize) / PageSize;
264*810390e3Srobert     PageSizeLog = getLog2(PageSize);
265*810390e3Srobert     RoundedRegionSize = PagesCount << PageSizeLog;
266*810390e3Srobert     RoundedSize = NumberOfRegions * RoundedRegionSize;
267*810390e3Srobert   }
2683cab2bb3Spatrick 
269*810390e3Srobert   // PageMap is lazily allocated when markFreeBlocks() is invoked.
hasBlockMarkedPageReleaseContext270*810390e3Srobert   bool hasBlockMarked() const {
271*810390e3Srobert     return PageMap.isAllocated();
272*810390e3Srobert   }
273*810390e3Srobert 
ensurePageMapAllocatedPageReleaseContext274*810390e3Srobert   void ensurePageMapAllocated() {
275*810390e3Srobert     if (PageMap.isAllocated())
276*810390e3Srobert       return;
277*810390e3Srobert     PageMap.reset(NumberOfRegions, PagesCount, FullPagesBlockCountMax);
278*810390e3Srobert     DCHECK(PageMap.isAllocated());
279*810390e3Srobert   }
280*810390e3Srobert 
281*810390e3Srobert   template<class TransferBatchT, typename DecompactPtrT>
markFreeBlocksPageReleaseContext282*810390e3Srobert   void markFreeBlocks(const IntrusiveList<TransferBatchT> &FreeList,
283*810390e3Srobert                       DecompactPtrT DecompactPtr, uptr Base) {
284*810390e3Srobert     ensurePageMapAllocated();
2853cab2bb3Spatrick 
2863cab2bb3Spatrick     // Iterate over free chunks and count how many free chunks affect each
2873cab2bb3Spatrick     // allocated page.
2883cab2bb3Spatrick     if (BlockSize <= PageSize && PageSize % BlockSize == 0) {
2893cab2bb3Spatrick       // Each chunk affects one page only.
2903cab2bb3Spatrick       for (const auto &It : FreeList) {
291*810390e3Srobert         for (u16 I = 0; I < It.getCount(); I++) {
292*810390e3Srobert           const uptr P = DecompactPtr(It.get(I)) - Base;
293d89ec533Spatrick           if (P >= RoundedSize)
294d89ec533Spatrick             continue;
295d89ec533Spatrick           const uptr RegionIndex = NumberOfRegions == 1U ? 0 : P / RegionSize;
296d89ec533Spatrick           const uptr PInRegion = P - RegionIndex * RegionSize;
297*810390e3Srobert           PageMap.inc(RegionIndex, PInRegion >> PageSizeLog);
2983cab2bb3Spatrick         }
2993cab2bb3Spatrick       }
3003cab2bb3Spatrick     } else {
3013cab2bb3Spatrick       // In all other cases chunks might affect more than one page.
302d89ec533Spatrick       DCHECK_GE(RegionSize, BlockSize);
303*810390e3Srobert       const uptr LastBlockInRegion =
304*810390e3Srobert           ((RegionSize / BlockSize) - 1U) * BlockSize;
3053cab2bb3Spatrick       for (const auto &It : FreeList) {
306*810390e3Srobert         for (u16 I = 0; I < It.getCount(); I++) {
307*810390e3Srobert           const uptr P = DecompactPtr(It.get(I)) - Base;
308d89ec533Spatrick           if (P >= RoundedSize)
309d89ec533Spatrick             continue;
310d89ec533Spatrick           const uptr RegionIndex = NumberOfRegions == 1U ? 0 : P / RegionSize;
311d89ec533Spatrick           uptr PInRegion = P - RegionIndex * RegionSize;
312*810390e3Srobert           PageMap.incRange(RegionIndex, PInRegion >> PageSizeLog,
313d89ec533Spatrick                             (PInRegion + BlockSize - 1) >> PageSizeLog);
314d89ec533Spatrick           // The last block in a region might straddle a page, so if it's
315d89ec533Spatrick           // free, we mark the following "pretend" memory block(s) as free.
316d89ec533Spatrick           if (PInRegion == LastBlockInRegion) {
317d89ec533Spatrick             PInRegion += BlockSize;
318d89ec533Spatrick             while (PInRegion < RoundedRegionSize) {
319*810390e3Srobert               PageMap.incRange(RegionIndex, PInRegion >> PageSizeLog,
320d89ec533Spatrick                                 (PInRegion + BlockSize - 1) >> PageSizeLog);
321d89ec533Spatrick               PInRegion += BlockSize;
3223cab2bb3Spatrick             }
3233cab2bb3Spatrick           }
324d89ec533Spatrick         }
325d89ec533Spatrick       }
3263cab2bb3Spatrick     }
327*810390e3Srobert   }
328*810390e3Srobert 
329*810390e3Srobert   uptr BlockSize;
330*810390e3Srobert   uptr RegionSize;
331*810390e3Srobert   uptr NumberOfRegions;
332*810390e3Srobert   uptr PageSize;
333*810390e3Srobert   uptr PagesCount;
334*810390e3Srobert   uptr PageSizeLog;
335*810390e3Srobert   uptr RoundedRegionSize;
336*810390e3Srobert   uptr RoundedSize;
337*810390e3Srobert   uptr FullPagesBlockCountMax;
338*810390e3Srobert   bool SameBlockCountPerPage;
339*810390e3Srobert   RegionPageMap PageMap;
340*810390e3Srobert };
341*810390e3Srobert 
342*810390e3Srobert // Try to release the page which doesn't have any in-used block, i.e., they are
343*810390e3Srobert // all free blocks. The `PageMap` will record the number of free blocks in each
344*810390e3Srobert // page.
345*810390e3Srobert template <class ReleaseRecorderT, typename SkipRegionT>
346*810390e3Srobert NOINLINE void
releaseFreeMemoryToOS(PageReleaseContext & Context,ReleaseRecorderT & Recorder,SkipRegionT SkipRegion)347*810390e3Srobert releaseFreeMemoryToOS(PageReleaseContext &Context,
348*810390e3Srobert                       ReleaseRecorderT &Recorder, SkipRegionT SkipRegion) {
349*810390e3Srobert   const uptr PageSize = Context.PageSize;
350*810390e3Srobert   const uptr BlockSize = Context.BlockSize;
351*810390e3Srobert   const uptr PagesCount = Context.PagesCount;
352*810390e3Srobert   const uptr NumberOfRegions = Context.NumberOfRegions;
353*810390e3Srobert   const uptr FullPagesBlockCountMax = Context.FullPagesBlockCountMax;
354*810390e3Srobert   const bool SameBlockCountPerPage = Context.SameBlockCountPerPage;
355*810390e3Srobert   RegionPageMap &PageMap = Context.PageMap;
3563cab2bb3Spatrick 
3573cab2bb3Spatrick   // Iterate over pages detecting ranges of pages with chunk Counters equal
3583cab2bb3Spatrick   // to the expected number of chunks for the particular page.
3593cab2bb3Spatrick   FreePagesRangeTracker<ReleaseRecorderT> RangeTracker(Recorder);
3603cab2bb3Spatrick   if (SameBlockCountPerPage) {
3613cab2bb3Spatrick     // Fast path, every page has the same number of chunks affecting it.
362d89ec533Spatrick     for (uptr I = 0; I < NumberOfRegions; I++) {
363d89ec533Spatrick       if (SkipRegion(I)) {
364d89ec533Spatrick         RangeTracker.skipPages(PagesCount);
365d89ec533Spatrick         continue;
366d89ec533Spatrick       }
367*810390e3Srobert       for (uptr J = 0; J < PagesCount; J++) {
368*810390e3Srobert         const bool CanRelease = PageMap.get(I, J) == FullPagesBlockCountMax;
369*810390e3Srobert         if (CanRelease)
370*810390e3Srobert           PageMap.setAsAllCounted(I, J);
371*810390e3Srobert         RangeTracker.processNextPage(CanRelease);
372*810390e3Srobert       }
373d89ec533Spatrick     }
3743cab2bb3Spatrick   } else {
3753cab2bb3Spatrick     // Slow path, go through the pages keeping count how many chunks affect
3763cab2bb3Spatrick     // each page.
3773cab2bb3Spatrick     const uptr Pn = BlockSize < PageSize ? PageSize / BlockSize : 1;
3783cab2bb3Spatrick     const uptr Pnc = Pn * BlockSize;
3793cab2bb3Spatrick     // The idea is to increment the current page pointer by the first chunk
3803cab2bb3Spatrick     // size, middle portion size (the portion of the page covered by chunks
3813cab2bb3Spatrick     // except the first and the last one) and then the last chunk size, adding
3823cab2bb3Spatrick     // up the number of chunks on the current page and checking on every step
3833cab2bb3Spatrick     // whether the page boundary was crossed.
384d89ec533Spatrick     for (uptr I = 0; I < NumberOfRegions; I++) {
385d89ec533Spatrick       if (SkipRegion(I)) {
386d89ec533Spatrick         RangeTracker.skipPages(PagesCount);
387d89ec533Spatrick         continue;
388d89ec533Spatrick       }
3893cab2bb3Spatrick       uptr PrevPageBoundary = 0;
3903cab2bb3Spatrick       uptr CurrentBoundary = 0;
391d89ec533Spatrick       for (uptr J = 0; J < PagesCount; J++) {
3923cab2bb3Spatrick         const uptr PageBoundary = PrevPageBoundary + PageSize;
3933cab2bb3Spatrick         uptr BlocksPerPage = Pn;
3943cab2bb3Spatrick         if (CurrentBoundary < PageBoundary) {
3953cab2bb3Spatrick           if (CurrentBoundary > PrevPageBoundary)
3963cab2bb3Spatrick             BlocksPerPage++;
3973cab2bb3Spatrick           CurrentBoundary += Pnc;
3983cab2bb3Spatrick           if (CurrentBoundary < PageBoundary) {
3993cab2bb3Spatrick             BlocksPerPage++;
4003cab2bb3Spatrick             CurrentBoundary += BlockSize;
4013cab2bb3Spatrick           }
4023cab2bb3Spatrick         }
4033cab2bb3Spatrick         PrevPageBoundary = PageBoundary;
404*810390e3Srobert         const bool CanRelease = PageMap.get(I, J) == BlocksPerPage;
405*810390e3Srobert         if (CanRelease)
406*810390e3Srobert           PageMap.setAsAllCounted(I, J);
407*810390e3Srobert         RangeTracker.processNextPage(CanRelease);
408d89ec533Spatrick       }
4093cab2bb3Spatrick     }
4103cab2bb3Spatrick   }
4113cab2bb3Spatrick   RangeTracker.finish();
4123cab2bb3Spatrick }
4133cab2bb3Spatrick 
414*810390e3Srobert // An overload releaseFreeMemoryToOS which doesn't require the page usage
415*810390e3Srobert // information after releasing.
416*810390e3Srobert template <class TransferBatchT, class ReleaseRecorderT, typename DecompactPtrT,
417*810390e3Srobert           typename SkipRegionT>
418*810390e3Srobert NOINLINE void
releaseFreeMemoryToOS(const IntrusiveList<TransferBatchT> & FreeList,uptr RegionSize,uptr NumberOfRegions,uptr BlockSize,ReleaseRecorderT & Recorder,DecompactPtrT DecompactPtr,SkipRegionT SkipRegion)419*810390e3Srobert releaseFreeMemoryToOS(const IntrusiveList<TransferBatchT> &FreeList,
420*810390e3Srobert                       uptr RegionSize, uptr NumberOfRegions, uptr BlockSize,
421*810390e3Srobert                       ReleaseRecorderT &Recorder, DecompactPtrT DecompactPtr,
422*810390e3Srobert                       SkipRegionT SkipRegion) {
423*810390e3Srobert   PageReleaseContext Context(BlockSize, RegionSize, NumberOfRegions);
424*810390e3Srobert   Context.markFreeBlocks(FreeList, DecompactPtr, Recorder.getBase());
425*810390e3Srobert   releaseFreeMemoryToOS(Context, Recorder, SkipRegion);
426*810390e3Srobert }
427*810390e3Srobert 
4283cab2bb3Spatrick } // namespace scudo
4293cab2bb3Spatrick 
4303cab2bb3Spatrick #endif // SCUDO_RELEASE_H_
431