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