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"
14*1f9cb04fSpatrick #include "mutex.h"
153cab2bb3Spatrick 
163cab2bb3Spatrick namespace scudo {
173cab2bb3Spatrick 
183cab2bb3Spatrick class ReleaseRecorder {
193cab2bb3Spatrick public:
203cab2bb3Spatrick   ReleaseRecorder(uptr BaseAddress, MapPlatformData *Data = nullptr)
213cab2bb3Spatrick       : BaseAddress(BaseAddress), Data(Data) {}
223cab2bb3Spatrick 
233cab2bb3Spatrick   uptr getReleasedRangesCount() const { return ReleasedRangesCount; }
243cab2bb3Spatrick 
253cab2bb3Spatrick   uptr getReleasedBytes() const { return ReleasedBytes; }
263cab2bb3Spatrick 
273cab2bb3Spatrick   // Releases [From, To) range of pages back to OS.
283cab2bb3Spatrick   void releasePageRangeToOS(uptr From, uptr To) {
293cab2bb3Spatrick     const uptr Size = To - From;
303cab2bb3Spatrick     releasePagesToOS(BaseAddress, From, Size, Data);
313cab2bb3Spatrick     ReleasedRangesCount++;
323cab2bb3Spatrick     ReleasedBytes += Size;
333cab2bb3Spatrick   }
343cab2bb3Spatrick 
353cab2bb3Spatrick private:
363cab2bb3Spatrick   uptr ReleasedRangesCount = 0;
373cab2bb3Spatrick   uptr ReleasedBytes = 0;
383cab2bb3Spatrick   uptr BaseAddress = 0;
393cab2bb3Spatrick   MapPlatformData *Data = nullptr;
403cab2bb3Spatrick };
413cab2bb3Spatrick 
423cab2bb3Spatrick // A packed array of Counters. Each counter occupies 2^N bits, enough to store
43*1f9cb04fSpatrick // counter's MaxValue. Ctor will try to use a static buffer first, and if that
44*1f9cb04fSpatrick // fails (the buffer is too small or already locked), will allocate the
45*1f9cb04fSpatrick // required Buffer via map(). The caller is expected to check whether the
46*1f9cb04fSpatrick // initialization was successful by checking isAllocated() result. For
47*1f9cb04fSpatrick // performance sake, none of the accessors check the validity of the arguments,
48*1f9cb04fSpatrick // It is assumed that Index is always in [0, N) range and the value is not
49*1f9cb04fSpatrick // incremented past MaxValue.
503cab2bb3Spatrick class PackedCounterArray {
513cab2bb3Spatrick public:
523cab2bb3Spatrick   PackedCounterArray(uptr NumCounters, uptr MaxValue) : N(NumCounters) {
533cab2bb3Spatrick     CHECK_GT(NumCounters, 0);
543cab2bb3Spatrick     CHECK_GT(MaxValue, 0);
553cab2bb3Spatrick     constexpr uptr MaxCounterBits = sizeof(*Buffer) * 8UL;
563cab2bb3Spatrick     // Rounding counter storage size up to the power of two allows for using
573cab2bb3Spatrick     // bit shifts calculating particular counter's Index and offset.
583cab2bb3Spatrick     const uptr CounterSizeBits =
593cab2bb3Spatrick         roundUpToPowerOfTwo(getMostSignificantSetBitIndex(MaxValue) + 1);
603cab2bb3Spatrick     CHECK_LE(CounterSizeBits, MaxCounterBits);
613cab2bb3Spatrick     CounterSizeBitsLog = getLog2(CounterSizeBits);
623cab2bb3Spatrick     CounterMask = ~(static_cast<uptr>(0)) >> (MaxCounterBits - CounterSizeBits);
633cab2bb3Spatrick 
643cab2bb3Spatrick     const uptr PackingRatio = MaxCounterBits >> CounterSizeBitsLog;
653cab2bb3Spatrick     CHECK_GT(PackingRatio, 0);
663cab2bb3Spatrick     PackingRatioLog = getLog2(PackingRatio);
673cab2bb3Spatrick     BitOffsetMask = PackingRatio - 1;
683cab2bb3Spatrick 
693cab2bb3Spatrick     BufferSize = (roundUpTo(N, static_cast<uptr>(1U) << PackingRatioLog) >>
703cab2bb3Spatrick                   PackingRatioLog) *
713cab2bb3Spatrick                  sizeof(*Buffer);
72*1f9cb04fSpatrick     if (BufferSize <= StaticBufferSize && Mutex.tryLock()) {
73*1f9cb04fSpatrick       Buffer = &StaticBuffer[0];
74*1f9cb04fSpatrick       memset(Buffer, 0, BufferSize);
75*1f9cb04fSpatrick     } else {
763cab2bb3Spatrick       Buffer = reinterpret_cast<uptr *>(
773cab2bb3Spatrick           map(nullptr, BufferSize, "scudo:counters", MAP_ALLOWNOMEM));
783cab2bb3Spatrick     }
79*1f9cb04fSpatrick   }
803cab2bb3Spatrick   ~PackedCounterArray() {
81*1f9cb04fSpatrick     if (!isAllocated())
82*1f9cb04fSpatrick       return;
83*1f9cb04fSpatrick     if (Buffer == &StaticBuffer[0])
84*1f9cb04fSpatrick       Mutex.unlock();
85*1f9cb04fSpatrick     else
863cab2bb3Spatrick       unmap(reinterpret_cast<void *>(Buffer), BufferSize);
873cab2bb3Spatrick   }
883cab2bb3Spatrick 
893cab2bb3Spatrick   bool isAllocated() const { return !!Buffer; }
903cab2bb3Spatrick 
913cab2bb3Spatrick   uptr getCount() const { return N; }
923cab2bb3Spatrick 
933cab2bb3Spatrick   uptr get(uptr I) const {
943cab2bb3Spatrick     DCHECK_LT(I, N);
953cab2bb3Spatrick     const uptr Index = I >> PackingRatioLog;
963cab2bb3Spatrick     const uptr BitOffset = (I & BitOffsetMask) << CounterSizeBitsLog;
973cab2bb3Spatrick     return (Buffer[Index] >> BitOffset) & CounterMask;
983cab2bb3Spatrick   }
993cab2bb3Spatrick 
1003cab2bb3Spatrick   void inc(uptr I) const {
1013cab2bb3Spatrick     DCHECK_LT(get(I), CounterMask);
1023cab2bb3Spatrick     const uptr Index = I >> PackingRatioLog;
1033cab2bb3Spatrick     const uptr BitOffset = (I & BitOffsetMask) << CounterSizeBitsLog;
1043cab2bb3Spatrick     DCHECK_LT(BitOffset, SCUDO_WORDSIZE);
1053cab2bb3Spatrick     Buffer[Index] += static_cast<uptr>(1U) << BitOffset;
1063cab2bb3Spatrick   }
1073cab2bb3Spatrick 
1083cab2bb3Spatrick   void incRange(uptr From, uptr To) const {
1093cab2bb3Spatrick     DCHECK_LE(From, To);
110*1f9cb04fSpatrick     const uptr Top = Min(To + 1, N);
111*1f9cb04fSpatrick     for (uptr I = From; I < Top; I++)
1123cab2bb3Spatrick       inc(I);
1133cab2bb3Spatrick   }
1143cab2bb3Spatrick 
1153cab2bb3Spatrick   uptr getBufferSize() const { return BufferSize; }
1163cab2bb3Spatrick 
1173cab2bb3Spatrick private:
1183cab2bb3Spatrick   const uptr N;
1193cab2bb3Spatrick   uptr CounterSizeBitsLog;
1203cab2bb3Spatrick   uptr CounterMask;
1213cab2bb3Spatrick   uptr PackingRatioLog;
1223cab2bb3Spatrick   uptr BitOffsetMask;
1233cab2bb3Spatrick 
1243cab2bb3Spatrick   uptr BufferSize;
1253cab2bb3Spatrick   uptr *Buffer;
126*1f9cb04fSpatrick 
127*1f9cb04fSpatrick   static HybridMutex Mutex;
128*1f9cb04fSpatrick   static const uptr StaticBufferSize = 1024U;
129*1f9cb04fSpatrick   static uptr StaticBuffer[StaticBufferSize];
1303cab2bb3Spatrick };
1313cab2bb3Spatrick 
1323cab2bb3Spatrick template <class ReleaseRecorderT> class FreePagesRangeTracker {
1333cab2bb3Spatrick public:
1343cab2bb3Spatrick   explicit FreePagesRangeTracker(ReleaseRecorderT *Recorder)
1353cab2bb3Spatrick       : Recorder(Recorder), PageSizeLog(getLog2(getPageSizeCached())) {}
1363cab2bb3Spatrick 
1373cab2bb3Spatrick   void processNextPage(bool Freed) {
1383cab2bb3Spatrick     if (Freed) {
1393cab2bb3Spatrick       if (!InRange) {
1403cab2bb3Spatrick         CurrentRangeStatePage = CurrentPage;
1413cab2bb3Spatrick         InRange = true;
1423cab2bb3Spatrick       }
1433cab2bb3Spatrick     } else {
1443cab2bb3Spatrick       closeOpenedRange();
1453cab2bb3Spatrick     }
1463cab2bb3Spatrick     CurrentPage++;
1473cab2bb3Spatrick   }
1483cab2bb3Spatrick 
1493cab2bb3Spatrick   void finish() { closeOpenedRange(); }
1503cab2bb3Spatrick 
1513cab2bb3Spatrick private:
1523cab2bb3Spatrick   void closeOpenedRange() {
1533cab2bb3Spatrick     if (InRange) {
1543cab2bb3Spatrick       Recorder->releasePageRangeToOS((CurrentRangeStatePage << PageSizeLog),
1553cab2bb3Spatrick                                      (CurrentPage << PageSizeLog));
1563cab2bb3Spatrick       InRange = false;
1573cab2bb3Spatrick     }
1583cab2bb3Spatrick   }
1593cab2bb3Spatrick 
1603cab2bb3Spatrick   ReleaseRecorderT *const Recorder;
1613cab2bb3Spatrick   const uptr PageSizeLog;
1623cab2bb3Spatrick   bool InRange = false;
1633cab2bb3Spatrick   uptr CurrentPage = 0;
1643cab2bb3Spatrick   uptr CurrentRangeStatePage = 0;
1653cab2bb3Spatrick };
1663cab2bb3Spatrick 
1673cab2bb3Spatrick template <class TransferBatchT, class ReleaseRecorderT>
1683cab2bb3Spatrick NOINLINE void
1693cab2bb3Spatrick releaseFreeMemoryToOS(const IntrusiveList<TransferBatchT> &FreeList, uptr Base,
170*1f9cb04fSpatrick                       uptr Size, uptr BlockSize, ReleaseRecorderT *Recorder) {
1713cab2bb3Spatrick   const uptr PageSize = getPageSizeCached();
1723cab2bb3Spatrick 
1733cab2bb3Spatrick   // Figure out the number of chunks per page and whether we can take a fast
1743cab2bb3Spatrick   // path (the number of chunks per page is the same for all pages).
1753cab2bb3Spatrick   uptr FullPagesBlockCountMax;
1763cab2bb3Spatrick   bool SameBlockCountPerPage;
1773cab2bb3Spatrick   if (BlockSize <= PageSize) {
1783cab2bb3Spatrick     if (PageSize % BlockSize == 0) {
1793cab2bb3Spatrick       // Same number of chunks per page, no cross overs.
1803cab2bb3Spatrick       FullPagesBlockCountMax = PageSize / BlockSize;
1813cab2bb3Spatrick       SameBlockCountPerPage = true;
1823cab2bb3Spatrick     } else if (BlockSize % (PageSize % BlockSize) == 0) {
1833cab2bb3Spatrick       // Some chunks are crossing page boundaries, which means that the page
1843cab2bb3Spatrick       // contains one or two partial chunks, but all pages contain the same
1853cab2bb3Spatrick       // number of chunks.
1863cab2bb3Spatrick       FullPagesBlockCountMax = PageSize / BlockSize + 1;
1873cab2bb3Spatrick       SameBlockCountPerPage = true;
1883cab2bb3Spatrick     } else {
1893cab2bb3Spatrick       // Some chunks are crossing page boundaries, which means that the page
1903cab2bb3Spatrick       // contains one or two partial chunks.
1913cab2bb3Spatrick       FullPagesBlockCountMax = PageSize / BlockSize + 2;
1923cab2bb3Spatrick       SameBlockCountPerPage = false;
1933cab2bb3Spatrick     }
1943cab2bb3Spatrick   } else {
1953cab2bb3Spatrick     if (BlockSize % PageSize == 0) {
1963cab2bb3Spatrick       // One chunk covers multiple pages, no cross overs.
1973cab2bb3Spatrick       FullPagesBlockCountMax = 1;
1983cab2bb3Spatrick       SameBlockCountPerPage = true;
1993cab2bb3Spatrick     } else {
2003cab2bb3Spatrick       // One chunk covers multiple pages, Some chunks are crossing page
2013cab2bb3Spatrick       // boundaries. Some pages contain one chunk, some contain two.
2023cab2bb3Spatrick       FullPagesBlockCountMax = 2;
2033cab2bb3Spatrick       SameBlockCountPerPage = false;
2043cab2bb3Spatrick     }
2053cab2bb3Spatrick   }
2063cab2bb3Spatrick 
207*1f9cb04fSpatrick   const uptr PagesCount = roundUpTo(Size, PageSize) / PageSize;
208*1f9cb04fSpatrick   PackedCounterArray Counters(PagesCount, FullPagesBlockCountMax);
2093cab2bb3Spatrick   if (!Counters.isAllocated())
2103cab2bb3Spatrick     return;
2113cab2bb3Spatrick 
2123cab2bb3Spatrick   const uptr PageSizeLog = getLog2(PageSize);
213*1f9cb04fSpatrick   const uptr RoundedSize = PagesCount << PageSizeLog;
2143cab2bb3Spatrick 
2153cab2bb3Spatrick   // Iterate over free chunks and count how many free chunks affect each
2163cab2bb3Spatrick   // allocated page.
2173cab2bb3Spatrick   if (BlockSize <= PageSize && PageSize % BlockSize == 0) {
2183cab2bb3Spatrick     // Each chunk affects one page only.
2193cab2bb3Spatrick     for (const auto &It : FreeList) {
220*1f9cb04fSpatrick       // If dealing with a TransferBatch, the first pointer of the batch will
221*1f9cb04fSpatrick       // point to the batch itself, we do not want to mark this for release as
222*1f9cb04fSpatrick       // the batch is in use, so skip the first entry.
223*1f9cb04fSpatrick       const bool IsTransferBatch =
224*1f9cb04fSpatrick           (It.getCount() != 0) &&
225*1f9cb04fSpatrick           (reinterpret_cast<uptr>(It.get(0)) == reinterpret_cast<uptr>(&It));
226*1f9cb04fSpatrick       for (u32 I = IsTransferBatch ? 1 : 0; I < It.getCount(); I++) {
227*1f9cb04fSpatrick         const uptr P = reinterpret_cast<uptr>(It.get(I)) - Base;
228*1f9cb04fSpatrick         // This takes care of P < Base and P >= Base + RoundedSize.
229*1f9cb04fSpatrick         if (P < RoundedSize)
230*1f9cb04fSpatrick           Counters.inc(P >> PageSizeLog);
2313cab2bb3Spatrick       }
2323cab2bb3Spatrick     }
233*1f9cb04fSpatrick     for (uptr P = Size; P < RoundedSize; P += BlockSize)
234*1f9cb04fSpatrick       Counters.inc(P >> PageSizeLog);
2353cab2bb3Spatrick   } else {
2363cab2bb3Spatrick     // In all other cases chunks might affect more than one page.
2373cab2bb3Spatrick     for (const auto &It : FreeList) {
238*1f9cb04fSpatrick       // See TransferBatch comment above.
239*1f9cb04fSpatrick       const bool IsTransferBatch =
240*1f9cb04fSpatrick           (It.getCount() != 0) &&
241*1f9cb04fSpatrick           (reinterpret_cast<uptr>(It.get(0)) == reinterpret_cast<uptr>(&It));
242*1f9cb04fSpatrick       for (u32 I = IsTransferBatch ? 1 : 0; I < It.getCount(); I++) {
243*1f9cb04fSpatrick         const uptr P = reinterpret_cast<uptr>(It.get(I)) - Base;
244*1f9cb04fSpatrick         // This takes care of P < Base and P >= Base + RoundedSize.
245*1f9cb04fSpatrick         if (P < RoundedSize)
246*1f9cb04fSpatrick           Counters.incRange(P >> PageSizeLog,
247*1f9cb04fSpatrick                             (P + BlockSize - 1) >> PageSizeLog);
2483cab2bb3Spatrick       }
2493cab2bb3Spatrick     }
250*1f9cb04fSpatrick     for (uptr P = Size; P < RoundedSize; P += BlockSize)
251*1f9cb04fSpatrick       Counters.incRange(P >> PageSizeLog, (P + BlockSize - 1) >> PageSizeLog);
2523cab2bb3Spatrick   }
2533cab2bb3Spatrick 
2543cab2bb3Spatrick   // Iterate over pages detecting ranges of pages with chunk Counters equal
2553cab2bb3Spatrick   // to the expected number of chunks for the particular page.
2563cab2bb3Spatrick   FreePagesRangeTracker<ReleaseRecorderT> RangeTracker(Recorder);
2573cab2bb3Spatrick   if (SameBlockCountPerPage) {
2583cab2bb3Spatrick     // Fast path, every page has the same number of chunks affecting it.
2593cab2bb3Spatrick     for (uptr I = 0; I < Counters.getCount(); I++)
2603cab2bb3Spatrick       RangeTracker.processNextPage(Counters.get(I) == FullPagesBlockCountMax);
2613cab2bb3Spatrick   } else {
2623cab2bb3Spatrick     // Slow path, go through the pages keeping count how many chunks affect
2633cab2bb3Spatrick     // each page.
2643cab2bb3Spatrick     const uptr Pn = BlockSize < PageSize ? PageSize / BlockSize : 1;
2653cab2bb3Spatrick     const uptr Pnc = Pn * BlockSize;
2663cab2bb3Spatrick     // The idea is to increment the current page pointer by the first chunk
2673cab2bb3Spatrick     // size, middle portion size (the portion of the page covered by chunks
2683cab2bb3Spatrick     // except the first and the last one) and then the last chunk size, adding
2693cab2bb3Spatrick     // up the number of chunks on the current page and checking on every step
2703cab2bb3Spatrick     // whether the page boundary was crossed.
2713cab2bb3Spatrick     uptr PrevPageBoundary = 0;
2723cab2bb3Spatrick     uptr CurrentBoundary = 0;
2733cab2bb3Spatrick     for (uptr I = 0; I < Counters.getCount(); I++) {
2743cab2bb3Spatrick       const uptr PageBoundary = PrevPageBoundary + PageSize;
2753cab2bb3Spatrick       uptr BlocksPerPage = Pn;
2763cab2bb3Spatrick       if (CurrentBoundary < PageBoundary) {
2773cab2bb3Spatrick         if (CurrentBoundary > PrevPageBoundary)
2783cab2bb3Spatrick           BlocksPerPage++;
2793cab2bb3Spatrick         CurrentBoundary += Pnc;
2803cab2bb3Spatrick         if (CurrentBoundary < PageBoundary) {
2813cab2bb3Spatrick           BlocksPerPage++;
2823cab2bb3Spatrick           CurrentBoundary += BlockSize;
2833cab2bb3Spatrick         }
2843cab2bb3Spatrick       }
2853cab2bb3Spatrick       PrevPageBoundary = PageBoundary;
2863cab2bb3Spatrick 
2873cab2bb3Spatrick       RangeTracker.processNextPage(Counters.get(I) == BlocksPerPage);
2883cab2bb3Spatrick     }
2893cab2bb3Spatrick   }
2903cab2bb3Spatrick   RangeTracker.finish();
2913cab2bb3Spatrick }
2923cab2bb3Spatrick 
2933cab2bb3Spatrick } // namespace scudo
2943cab2bb3Spatrick 
2953cab2bb3Spatrick #endif // SCUDO_RELEASE_H_
296