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 NumberOfRegions, uptr CountersPerRegion, 53 uptr MaxValue) 54 : Regions(NumberOfRegions), NumCounters(CountersPerRegion) { 55 DCHECK_GT(Regions, 0); 56 DCHECK_GT(NumCounters, 0); 57 DCHECK_GT(MaxValue, 0); 58 constexpr uptr MaxCounterBits = sizeof(*Buffer) * 8UL; 59 // Rounding counter storage size up to the power of two allows for using 60 // bit shifts calculating particular counter's Index and offset. 61 const uptr CounterSizeBits = 62 roundUpToPowerOfTwo(getMostSignificantSetBitIndex(MaxValue) + 1); 63 DCHECK_LE(CounterSizeBits, MaxCounterBits); 64 CounterSizeBitsLog = getLog2(CounterSizeBits); 65 CounterMask = ~(static_cast<uptr>(0)) >> (MaxCounterBits - CounterSizeBits); 66 67 const uptr PackingRatio = MaxCounterBits >> CounterSizeBitsLog; 68 DCHECK_GT(PackingRatio, 0); 69 PackingRatioLog = getLog2(PackingRatio); 70 BitOffsetMask = PackingRatio - 1; 71 72 SizePerRegion = 73 roundUpTo(NumCounters, static_cast<uptr>(1U) << PackingRatioLog) >> 74 PackingRatioLog; 75 BufferSize = SizePerRegion * sizeof(*Buffer) * Regions; 76 if (BufferSize <= (StaticBufferCount * sizeof(Buffer[0])) && 77 Mutex.tryLock()) { 78 Buffer = &StaticBuffer[0]; 79 memset(Buffer, 0, BufferSize); 80 } else { 81 Buffer = reinterpret_cast<uptr *>( 82 map(nullptr, BufferSize, "scudo:counters", MAP_ALLOWNOMEM)); 83 } 84 } 85 ~PackedCounterArray() { 86 if (!isAllocated()) 87 return; 88 if (Buffer == &StaticBuffer[0]) 89 Mutex.unlock(); 90 else 91 unmap(reinterpret_cast<void *>(Buffer), BufferSize); 92 } 93 94 bool isAllocated() const { return !!Buffer; } 95 96 uptr getCount() const { return NumCounters; } 97 98 uptr get(uptr Region, uptr I) const { 99 DCHECK_LT(Region, Regions); 100 DCHECK_LT(I, NumCounters); 101 const uptr Index = I >> PackingRatioLog; 102 const uptr BitOffset = (I & BitOffsetMask) << CounterSizeBitsLog; 103 return (Buffer[Region * SizePerRegion + Index] >> BitOffset) & CounterMask; 104 } 105 106 void inc(uptr Region, uptr I) const { 107 DCHECK_LT(get(Region, I), CounterMask); 108 const uptr Index = I >> PackingRatioLog; 109 const uptr BitOffset = (I & BitOffsetMask) << CounterSizeBitsLog; 110 DCHECK_LT(BitOffset, SCUDO_WORDSIZE); 111 Buffer[Region * SizePerRegion + Index] += static_cast<uptr>(1U) 112 << BitOffset; 113 } 114 115 void incRange(uptr Region, uptr From, uptr To) const { 116 DCHECK_LE(From, To); 117 const uptr Top = Min(To + 1, NumCounters); 118 for (uptr I = From; I < Top; I++) 119 inc(Region, I); 120 } 121 122 uptr getBufferSize() const { return BufferSize; } 123 124 static const uptr StaticBufferCount = 2048U; 125 126 private: 127 const uptr Regions; 128 const uptr NumCounters; 129 uptr CounterSizeBitsLog; 130 uptr CounterMask; 131 uptr PackingRatioLog; 132 uptr BitOffsetMask; 133 134 uptr SizePerRegion; 135 uptr BufferSize; 136 uptr *Buffer; 137 138 static HybridMutex Mutex; 139 static uptr StaticBuffer[StaticBufferCount]; 140 }; 141 142 template <class ReleaseRecorderT> class FreePagesRangeTracker { 143 public: 144 explicit FreePagesRangeTracker(ReleaseRecorderT *Recorder) 145 : Recorder(Recorder), PageSizeLog(getLog2(getPageSizeCached())) {} 146 147 void processNextPage(bool Freed) { 148 if (Freed) { 149 if (!InRange) { 150 CurrentRangeStatePage = CurrentPage; 151 InRange = true; 152 } 153 } else { 154 closeOpenedRange(); 155 } 156 CurrentPage++; 157 } 158 159 void skipPages(uptr N) { 160 closeOpenedRange(); 161 CurrentPage += N; 162 } 163 164 void finish() { closeOpenedRange(); } 165 166 private: 167 void closeOpenedRange() { 168 if (InRange) { 169 Recorder->releasePageRangeToOS((CurrentRangeStatePage << PageSizeLog), 170 (CurrentPage << PageSizeLog)); 171 InRange = false; 172 } 173 } 174 175 ReleaseRecorderT *const Recorder; 176 const uptr PageSizeLog; 177 bool InRange = false; 178 uptr CurrentPage = 0; 179 uptr CurrentRangeStatePage = 0; 180 }; 181 182 template <class TransferBatchT, class ReleaseRecorderT, typename SkipRegionT> 183 NOINLINE void 184 releaseFreeMemoryToOS(const IntrusiveList<TransferBatchT> &FreeList, uptr Base, 185 uptr RegionSize, uptr NumberOfRegions, uptr BlockSize, 186 ReleaseRecorderT *Recorder, SkipRegionT SkipRegion) { 187 const uptr PageSize = getPageSizeCached(); 188 189 // Figure out the number of chunks per page and whether we can take a fast 190 // path (the number of chunks per page is the same for all pages). 191 uptr FullPagesBlockCountMax; 192 bool SameBlockCountPerPage; 193 if (BlockSize <= PageSize) { 194 if (PageSize % BlockSize == 0) { 195 // Same number of chunks per page, no cross overs. 196 FullPagesBlockCountMax = PageSize / BlockSize; 197 SameBlockCountPerPage = true; 198 } else if (BlockSize % (PageSize % BlockSize) == 0) { 199 // Some chunks are crossing page boundaries, which means that the page 200 // contains one or two partial chunks, but all pages contain the same 201 // number of chunks. 202 FullPagesBlockCountMax = PageSize / BlockSize + 1; 203 SameBlockCountPerPage = true; 204 } else { 205 // Some chunks are crossing page boundaries, which means that the page 206 // contains one or two partial chunks. 207 FullPagesBlockCountMax = PageSize / BlockSize + 2; 208 SameBlockCountPerPage = false; 209 } 210 } else { 211 if (BlockSize % PageSize == 0) { 212 // One chunk covers multiple pages, no cross overs. 213 FullPagesBlockCountMax = 1; 214 SameBlockCountPerPage = true; 215 } else { 216 // One chunk covers multiple pages, Some chunks are crossing page 217 // boundaries. Some pages contain one chunk, some contain two. 218 FullPagesBlockCountMax = 2; 219 SameBlockCountPerPage = false; 220 } 221 } 222 223 const uptr PagesCount = roundUpTo(RegionSize, PageSize) / PageSize; 224 PackedCounterArray Counters(NumberOfRegions, PagesCount, 225 FullPagesBlockCountMax); 226 if (!Counters.isAllocated()) 227 return; 228 229 const uptr PageSizeLog = getLog2(PageSize); 230 const uptr RoundedRegionSize = PagesCount << PageSizeLog; 231 const uptr RoundedSize = NumberOfRegions * RoundedRegionSize; 232 233 // Iterate over free chunks and count how many free chunks affect each 234 // allocated page. 235 if (BlockSize <= PageSize && PageSize % BlockSize == 0) { 236 // Each chunk affects one page only. 237 for (const auto &It : FreeList) { 238 for (u32 I = 0; I < It.getCount(); I++) { 239 const uptr P = reinterpret_cast<uptr>(It.get(I)) - Base; 240 // This takes care of P < Base and P >= Base + RoundedSize. 241 if (UNLIKELY(P >= RoundedSize)) 242 continue; 243 const uptr RegionIndex = NumberOfRegions == 1U ? 0 : P / RegionSize; 244 const uptr PInRegion = P - RegionIndex * RegionSize; 245 Counters.inc(RegionIndex, PInRegion >> PageSizeLog); 246 } 247 } 248 } else { 249 // In all other cases chunks might affect more than one page. 250 DCHECK_GE(RegionSize, BlockSize); 251 const uptr LastBlockInRegion = ((RegionSize / BlockSize) - 1U) * BlockSize; 252 for (const auto &It : FreeList) { 253 for (u32 I = 0; I < It.getCount(); I++) { 254 const uptr P = reinterpret_cast<uptr>(It.get(I)) - Base; 255 // This takes care of P < Base and P >= Base + RoundedSize. 256 if (UNLIKELY(P >= RoundedSize)) 257 continue; 258 const uptr RegionIndex = NumberOfRegions == 1U ? 0 : P / RegionSize; 259 uptr PInRegion = P - RegionIndex * RegionSize; 260 Counters.incRange(RegionIndex, PInRegion >> PageSizeLog, 261 (PInRegion + BlockSize - 1) >> PageSizeLog); 262 // The last block in a region might straddle a page, so if it's 263 // free, we mark the following "pretend" memory block(s) as free. 264 if (PInRegion == LastBlockInRegion) { 265 PInRegion += BlockSize; 266 while (PInRegion < RoundedRegionSize) { 267 Counters.incRange(RegionIndex, PInRegion >> PageSizeLog, 268 (PInRegion + BlockSize - 1) >> PageSizeLog); 269 PInRegion += BlockSize; 270 } 271 } 272 } 273 } 274 } 275 276 // Iterate over pages detecting ranges of pages with chunk Counters equal 277 // to the expected number of chunks for the particular page. 278 FreePagesRangeTracker<ReleaseRecorderT> RangeTracker(Recorder); 279 if (SameBlockCountPerPage) { 280 // Fast path, every page has the same number of chunks affecting it. 281 for (uptr I = 0; I < NumberOfRegions; I++) { 282 if (SkipRegion(I)) { 283 RangeTracker.skipPages(PagesCount); 284 continue; 285 } 286 for (uptr J = 0; J < PagesCount; J++) 287 RangeTracker.processNextPage(Counters.get(I, J) == 288 FullPagesBlockCountMax); 289 } 290 } else { 291 // Slow path, go through the pages keeping count how many chunks affect 292 // each page. 293 const uptr Pn = BlockSize < PageSize ? PageSize / BlockSize : 1; 294 const uptr Pnc = Pn * BlockSize; 295 // The idea is to increment the current page pointer by the first chunk 296 // size, middle portion size (the portion of the page covered by chunks 297 // except the first and the last one) and then the last chunk size, adding 298 // up the number of chunks on the current page and checking on every step 299 // whether the page boundary was crossed. 300 for (uptr I = 0; I < NumberOfRegions; I++) { 301 if (SkipRegion(I)) { 302 RangeTracker.skipPages(PagesCount); 303 continue; 304 } 305 uptr PrevPageBoundary = 0; 306 uptr CurrentBoundary = 0; 307 for (uptr J = 0; J < PagesCount; J++) { 308 const uptr PageBoundary = PrevPageBoundary + PageSize; 309 uptr BlocksPerPage = Pn; 310 if (CurrentBoundary < PageBoundary) { 311 if (CurrentBoundary > PrevPageBoundary) 312 BlocksPerPage++; 313 CurrentBoundary += Pnc; 314 if (CurrentBoundary < PageBoundary) { 315 BlocksPerPage++; 316 CurrentBoundary += BlockSize; 317 } 318 } 319 PrevPageBoundary = PageBoundary; 320 RangeTracker.processNextPage(Counters.get(I, J) == BlocksPerPage); 321 } 322 } 323 } 324 RangeTracker.finish(); 325 } 326 327 } // namespace scudo 328 329 #endif // SCUDO_RELEASE_H_ 330