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 15 namespace scudo { 16 17 class ReleaseRecorder { 18 public: 19 ReleaseRecorder(uptr BaseAddress, MapPlatformData *Data = nullptr) 20 : BaseAddress(BaseAddress), Data(Data) {} 21 22 uptr getReleasedRangesCount() const { return ReleasedRangesCount; } 23 24 uptr getReleasedBytes() const { return ReleasedBytes; } 25 26 // Releases [From, To) range of pages back to OS. 27 void releasePageRangeToOS(uptr From, uptr To) { 28 const uptr Size = To - From; 29 releasePagesToOS(BaseAddress, From, Size, Data); 30 ReleasedRangesCount++; 31 ReleasedBytes += Size; 32 } 33 34 private: 35 uptr ReleasedRangesCount = 0; 36 uptr ReleasedBytes = 0; 37 uptr BaseAddress = 0; 38 MapPlatformData *Data = nullptr; 39 }; 40 41 // A packed array of Counters. Each counter occupies 2^N bits, enough to store 42 // counter's MaxValue. Ctor will try to allocate the required Buffer via map() 43 // and the caller is expected to check whether the initialization was successful 44 // by checking isAllocated() result. For the performance sake, none of the 45 // accessors check the validity of the arguments, It is assumed that Index is 46 // always in [0, N) range and the value is not incremented past MaxValue. 47 class PackedCounterArray { 48 public: 49 PackedCounterArray(uptr NumCounters, uptr MaxValue) : N(NumCounters) { 50 CHECK_GT(NumCounters, 0); 51 CHECK_GT(MaxValue, 0); 52 constexpr uptr MaxCounterBits = sizeof(*Buffer) * 8UL; 53 // Rounding counter storage size up to the power of two allows for using 54 // bit shifts calculating particular counter's Index and offset. 55 const uptr CounterSizeBits = 56 roundUpToPowerOfTwo(getMostSignificantSetBitIndex(MaxValue) + 1); 57 CHECK_LE(CounterSizeBits, MaxCounterBits); 58 CounterSizeBitsLog = getLog2(CounterSizeBits); 59 CounterMask = ~(static_cast<uptr>(0)) >> (MaxCounterBits - CounterSizeBits); 60 61 const uptr PackingRatio = MaxCounterBits >> CounterSizeBitsLog; 62 CHECK_GT(PackingRatio, 0); 63 PackingRatioLog = getLog2(PackingRatio); 64 BitOffsetMask = PackingRatio - 1; 65 66 BufferSize = (roundUpTo(N, static_cast<uptr>(1U) << PackingRatioLog) >> 67 PackingRatioLog) * 68 sizeof(*Buffer); 69 Buffer = reinterpret_cast<uptr *>( 70 map(nullptr, BufferSize, "scudo:counters", MAP_ALLOWNOMEM)); 71 } 72 ~PackedCounterArray() { 73 if (isAllocated()) 74 unmap(reinterpret_cast<void *>(Buffer), BufferSize); 75 } 76 77 bool isAllocated() const { return !!Buffer; } 78 79 uptr getCount() const { return N; } 80 81 uptr get(uptr I) const { 82 DCHECK_LT(I, N); 83 const uptr Index = I >> PackingRatioLog; 84 const uptr BitOffset = (I & BitOffsetMask) << CounterSizeBitsLog; 85 return (Buffer[Index] >> BitOffset) & CounterMask; 86 } 87 88 void inc(uptr I) const { 89 DCHECK_LT(get(I), CounterMask); 90 const uptr Index = I >> PackingRatioLog; 91 const uptr BitOffset = (I & BitOffsetMask) << CounterSizeBitsLog; 92 DCHECK_LT(BitOffset, SCUDO_WORDSIZE); 93 Buffer[Index] += static_cast<uptr>(1U) << BitOffset; 94 } 95 96 void incRange(uptr From, uptr To) const { 97 DCHECK_LE(From, To); 98 for (uptr I = From; I <= To; I++) 99 inc(I); 100 } 101 102 uptr getBufferSize() const { return BufferSize; } 103 104 private: 105 const uptr N; 106 uptr CounterSizeBitsLog; 107 uptr CounterMask; 108 uptr PackingRatioLog; 109 uptr BitOffsetMask; 110 111 uptr BufferSize; 112 uptr *Buffer; 113 }; 114 115 template <class ReleaseRecorderT> class FreePagesRangeTracker { 116 public: 117 explicit FreePagesRangeTracker(ReleaseRecorderT *Recorder) 118 : Recorder(Recorder), PageSizeLog(getLog2(getPageSizeCached())) {} 119 120 void processNextPage(bool Freed) { 121 if (Freed) { 122 if (!InRange) { 123 CurrentRangeStatePage = CurrentPage; 124 InRange = true; 125 } 126 } else { 127 closeOpenedRange(); 128 } 129 CurrentPage++; 130 } 131 132 void finish() { closeOpenedRange(); } 133 134 private: 135 void closeOpenedRange() { 136 if (InRange) { 137 Recorder->releasePageRangeToOS((CurrentRangeStatePage << PageSizeLog), 138 (CurrentPage << PageSizeLog)); 139 InRange = false; 140 } 141 } 142 143 ReleaseRecorderT *const Recorder; 144 const uptr PageSizeLog; 145 bool InRange = false; 146 uptr CurrentPage = 0; 147 uptr CurrentRangeStatePage = 0; 148 }; 149 150 template <class TransferBatchT, class ReleaseRecorderT> 151 NOINLINE void 152 releaseFreeMemoryToOS(const IntrusiveList<TransferBatchT> &FreeList, uptr Base, 153 uptr AllocatedPagesCount, uptr BlockSize, 154 ReleaseRecorderT *Recorder) { 155 const uptr PageSize = getPageSizeCached(); 156 157 // Figure out the number of chunks per page and whether we can take a fast 158 // path (the number of chunks per page is the same for all pages). 159 uptr FullPagesBlockCountMax; 160 bool SameBlockCountPerPage; 161 if (BlockSize <= PageSize) { 162 if (PageSize % BlockSize == 0) { 163 // Same number of chunks per page, no cross overs. 164 FullPagesBlockCountMax = PageSize / BlockSize; 165 SameBlockCountPerPage = true; 166 } else if (BlockSize % (PageSize % BlockSize) == 0) { 167 // Some chunks are crossing page boundaries, which means that the page 168 // contains one or two partial chunks, but all pages contain the same 169 // number of chunks. 170 FullPagesBlockCountMax = PageSize / BlockSize + 1; 171 SameBlockCountPerPage = true; 172 } else { 173 // Some chunks are crossing page boundaries, which means that the page 174 // contains one or two partial chunks. 175 FullPagesBlockCountMax = PageSize / BlockSize + 2; 176 SameBlockCountPerPage = false; 177 } 178 } else { 179 if (BlockSize % PageSize == 0) { 180 // One chunk covers multiple pages, no cross overs. 181 FullPagesBlockCountMax = 1; 182 SameBlockCountPerPage = true; 183 } else { 184 // One chunk covers multiple pages, Some chunks are crossing page 185 // boundaries. Some pages contain one chunk, some contain two. 186 FullPagesBlockCountMax = 2; 187 SameBlockCountPerPage = false; 188 } 189 } 190 191 PackedCounterArray Counters(AllocatedPagesCount, FullPagesBlockCountMax); 192 if (!Counters.isAllocated()) 193 return; 194 195 const uptr PageSizeLog = getLog2(PageSize); 196 const uptr End = Base + AllocatedPagesCount * PageSize; 197 198 // Iterate over free chunks and count how many free chunks affect each 199 // allocated page. 200 if (BlockSize <= PageSize && PageSize % BlockSize == 0) { 201 // Each chunk affects one page only. 202 for (const auto &It : FreeList) { 203 for (u32 I = 0; I < It.getCount(); I++) { 204 const uptr P = reinterpret_cast<uptr>(It.get(I)); 205 if (P >= Base && P < End) 206 Counters.inc((P - Base) >> PageSizeLog); 207 } 208 } 209 } else { 210 // In all other cases chunks might affect more than one page. 211 for (const auto &It : FreeList) { 212 for (u32 I = 0; I < It.getCount(); I++) { 213 const uptr P = reinterpret_cast<uptr>(It.get(I)); 214 if (P >= Base && P < End) 215 Counters.incRange((P - Base) >> PageSizeLog, 216 (P - Base + BlockSize - 1) >> PageSizeLog); 217 } 218 } 219 } 220 221 // Iterate over pages detecting ranges of pages with chunk Counters equal 222 // to the expected number of chunks for the particular page. 223 FreePagesRangeTracker<ReleaseRecorderT> RangeTracker(Recorder); 224 if (SameBlockCountPerPage) { 225 // Fast path, every page has the same number of chunks affecting it. 226 for (uptr I = 0; I < Counters.getCount(); I++) 227 RangeTracker.processNextPage(Counters.get(I) == FullPagesBlockCountMax); 228 } else { 229 // Slow path, go through the pages keeping count how many chunks affect 230 // each page. 231 const uptr Pn = BlockSize < PageSize ? PageSize / BlockSize : 1; 232 const uptr Pnc = Pn * BlockSize; 233 // The idea is to increment the current page pointer by the first chunk 234 // size, middle portion size (the portion of the page covered by chunks 235 // except the first and the last one) and then the last chunk size, adding 236 // up the number of chunks on the current page and checking on every step 237 // whether the page boundary was crossed. 238 uptr PrevPageBoundary = 0; 239 uptr CurrentBoundary = 0; 240 for (uptr I = 0; I < Counters.getCount(); I++) { 241 const uptr PageBoundary = PrevPageBoundary + PageSize; 242 uptr BlocksPerPage = Pn; 243 if (CurrentBoundary < PageBoundary) { 244 if (CurrentBoundary > PrevPageBoundary) 245 BlocksPerPage++; 246 CurrentBoundary += Pnc; 247 if (CurrentBoundary < PageBoundary) { 248 BlocksPerPage++; 249 CurrentBoundary += BlockSize; 250 } 251 } 252 PrevPageBoundary = PageBoundary; 253 254 RangeTracker.processNextPage(Counters.get(I) == BlocksPerPage); 255 } 256 } 257 RangeTracker.finish(); 258 } 259 260 } // namespace scudo 261 262 #endif // SCUDO_RELEASE_H_ 263