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