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 "mem_map.h" 15 #include "mutex.h" 16 #include "thread_annotations.h" 17 18 namespace scudo { 19 20 template <typename MemMapT> class RegionReleaseRecorder { 21 public: 22 RegionReleaseRecorder(MemMapT *RegionMemMap, uptr Base, uptr Offset = 0) 23 : RegionMemMap(RegionMemMap), Base(Base), Offset(Offset) {} 24 25 uptr getReleasedBytes() const { return ReleasedBytes; } 26 27 uptr getBase() const { return Base; } 28 29 // Releases [From, To) range of pages back to OS. Note that `From` and `To` 30 // are offseted from `Base` + Offset. 31 void releasePageRangeToOS(uptr From, uptr To) { 32 const uptr Size = To - From; 33 RegionMemMap->releasePagesToOS(getBase() + Offset + From, Size); 34 ReleasedBytes += Size; 35 } 36 37 private: 38 uptr ReleasedBytes = 0; 39 MemMapT *RegionMemMap = nullptr; 40 uptr Base = 0; 41 // The release offset from Base. This is used when we know a given range after 42 // Base will not be released. 43 uptr Offset = 0; 44 }; 45 46 class ReleaseRecorder { 47 public: 48 ReleaseRecorder(uptr Base, uptr Offset = 0, MapPlatformData *Data = nullptr) 49 : Base(Base), Offset(Offset), Data(Data) {} 50 51 uptr getReleasedBytes() const { return ReleasedBytes; } 52 53 uptr getBase() const { return Base; } 54 55 // Releases [From, To) range of pages back to OS. 56 void releasePageRangeToOS(uptr From, uptr To) { 57 const uptr Size = To - From; 58 releasePagesToOS(Base, From + Offset, Size, Data); 59 ReleasedBytes += Size; 60 } 61 62 private: 63 uptr ReleasedBytes = 0; 64 // The starting address to release. Note that we may want to combine (Base + 65 // Offset) as a new Base. However, the Base is retrieved from 66 // `MapPlatformData` on Fuchsia, which means the offset won't be aware. 67 // Therefore, store them separately to make it work on all the platforms. 68 uptr Base = 0; 69 // The release offset from Base. This is used when we know a given range after 70 // Base will not be released. 71 uptr Offset = 0; 72 MapPlatformData *Data = nullptr; 73 }; 74 75 class FragmentationRecorder { 76 public: 77 FragmentationRecorder() = default; 78 79 uptr getReleasedPagesCount() const { return ReleasedPagesCount; } 80 81 void releasePageRangeToOS(uptr From, uptr To) { 82 DCHECK_EQ((To - From) % getPageSizeCached(), 0U); 83 ReleasedPagesCount += (To - From) >> getPageSizeLogCached(); 84 } 85 86 private: 87 uptr ReleasedPagesCount = 0; 88 }; 89 90 template <uptr GroupSize, uptr NumGroups> 91 class MemoryGroupFragmentationRecorder { 92 public: 93 const uptr NumPagesInOneGroup = GroupSize / getPageSizeCached(); 94 95 void releasePageRangeToOS(uptr From, uptr To) { 96 for (uptr I = From / getPageSizeCached(); I < To / getPageSizeCached(); ++I) 97 ++FreePagesCount[I / NumPagesInOneGroup]; 98 } 99 100 uptr getNumFreePages(uptr GroupId) { return FreePagesCount[GroupId]; } 101 102 private: 103 uptr FreePagesCount[NumGroups] = {}; 104 }; 105 106 // A buffer pool which holds a fixed number of static buffers of `uptr` elements 107 // for fast buffer allocation. If the request size is greater than 108 // `StaticBufferNumElements` or if all the static buffers are in use, it'll 109 // delegate the allocation to map(). 110 template <uptr StaticBufferCount, uptr StaticBufferNumElements> 111 class BufferPool { 112 public: 113 // Preserve 1 bit in the `Mask` so that we don't need to do zero-check while 114 // extracting the least significant bit from the `Mask`. 115 static_assert(StaticBufferCount < SCUDO_WORDSIZE, ""); 116 static_assert(isAligned(StaticBufferNumElements * sizeof(uptr), 117 SCUDO_CACHE_LINE_SIZE), 118 ""); 119 120 struct Buffer { 121 // Pointer to the buffer's memory, or nullptr if no buffer was allocated. 122 uptr *Data = nullptr; 123 124 // The index of the underlying static buffer, or StaticBufferCount if this 125 // buffer was dynamically allocated. This value is initially set to a poison 126 // value to aid debugging. 127 uptr BufferIndex = ~static_cast<uptr>(0); 128 129 // Only valid if BufferIndex == StaticBufferCount. 130 MemMapT MemMap = {}; 131 }; 132 133 // Return a zero-initialized buffer which can contain at least the given 134 // number of elements, or nullptr on failure. 135 Buffer getBuffer(const uptr NumElements) { 136 if (UNLIKELY(NumElements > StaticBufferNumElements)) 137 return getDynamicBuffer(NumElements); 138 139 uptr index; 140 { 141 // TODO: In general, we expect this operation should be fast so the 142 // waiting thread won't be put into sleep. The HybridMutex does implement 143 // the busy-waiting but we may want to review the performance and see if 144 // we need an explict spin lock here. 145 ScopedLock L(Mutex); 146 index = getLeastSignificantSetBitIndex(Mask); 147 if (index < StaticBufferCount) 148 Mask ^= static_cast<uptr>(1) << index; 149 } 150 151 if (index >= StaticBufferCount) 152 return getDynamicBuffer(NumElements); 153 154 Buffer Buf; 155 Buf.Data = &RawBuffer[index * StaticBufferNumElements]; 156 Buf.BufferIndex = index; 157 memset(Buf.Data, 0, StaticBufferNumElements * sizeof(uptr)); 158 return Buf; 159 } 160 161 void releaseBuffer(Buffer Buf) { 162 DCHECK_NE(Buf.Data, nullptr); 163 DCHECK_LE(Buf.BufferIndex, StaticBufferCount); 164 if (Buf.BufferIndex != StaticBufferCount) { 165 ScopedLock L(Mutex); 166 DCHECK_EQ((Mask & (static_cast<uptr>(1) << Buf.BufferIndex)), 0U); 167 Mask |= static_cast<uptr>(1) << Buf.BufferIndex; 168 } else { 169 Buf.MemMap.unmap(); 170 } 171 } 172 173 bool isStaticBufferTestOnly(const Buffer &Buf) { 174 DCHECK_NE(Buf.Data, nullptr); 175 DCHECK_LE(Buf.BufferIndex, StaticBufferCount); 176 return Buf.BufferIndex != StaticBufferCount; 177 } 178 179 private: 180 Buffer getDynamicBuffer(const uptr NumElements) { 181 // When using a heap-based buffer, precommit the pages backing the 182 // Vmar by passing |MAP_PRECOMMIT| flag. This allows an optimization 183 // where page fault exceptions are skipped as the allocated memory 184 // is accessed. So far, this is only enabled on Fuchsia. It hasn't proven a 185 // performance benefit on other platforms. 186 const uptr MmapFlags = MAP_ALLOWNOMEM | (SCUDO_FUCHSIA ? MAP_PRECOMMIT : 0); 187 const uptr MappedSize = 188 roundUp(NumElements * sizeof(uptr), getPageSizeCached()); 189 Buffer Buf; 190 if (Buf.MemMap.map(/*Addr=*/0, MappedSize, "scudo:counters", MmapFlags)) { 191 Buf.Data = reinterpret_cast<uptr *>(Buf.MemMap.getBase()); 192 Buf.BufferIndex = StaticBufferCount; 193 } 194 return Buf; 195 } 196 197 HybridMutex Mutex; 198 // '1' means that buffer index is not used. '0' means the buffer is in use. 199 uptr Mask GUARDED_BY(Mutex) = ~static_cast<uptr>(0); 200 uptr RawBuffer[StaticBufferCount * StaticBufferNumElements] GUARDED_BY(Mutex); 201 }; 202 203 // A Region page map is used to record the usage of pages in the regions. It 204 // implements a packed array of Counters. Each counter occupies 2^N bits, enough 205 // to store counter's MaxValue. Ctor will try to use a static buffer first, and 206 // if that fails (the buffer is too small or already locked), will allocate the 207 // required Buffer via map(). The caller is expected to check whether the 208 // initialization was successful by checking isAllocated() result. For 209 // performance sake, none of the accessors check the validity of the arguments, 210 // It is assumed that Index is always in [0, N) range and the value is not 211 // incremented past MaxValue. 212 class RegionPageMap { 213 public: 214 RegionPageMap() 215 : Regions(0), NumCounters(0), CounterSizeBitsLog(0), CounterMask(0), 216 PackingRatioLog(0), BitOffsetMask(0), SizePerRegion(0), 217 BufferNumElements(0) {} 218 RegionPageMap(uptr NumberOfRegions, uptr CountersPerRegion, uptr MaxValue) { 219 reset(NumberOfRegions, CountersPerRegion, MaxValue); 220 } 221 ~RegionPageMap() { 222 if (!isAllocated()) 223 return; 224 Buffers.releaseBuffer(Buffer); 225 Buffer = {}; 226 } 227 228 // Lock of `StaticBuffer` is acquired conditionally and there's no easy way to 229 // specify the thread-safety attribute properly in current code structure. 230 // Besides, it's the only place we may want to check thread safety. Therefore, 231 // it's fine to bypass the thread-safety analysis now. 232 void reset(uptr NumberOfRegion, uptr CountersPerRegion, uptr MaxValue) { 233 DCHECK_GT(NumberOfRegion, 0); 234 DCHECK_GT(CountersPerRegion, 0); 235 DCHECK_GT(MaxValue, 0); 236 237 Regions = NumberOfRegion; 238 NumCounters = CountersPerRegion; 239 240 constexpr uptr MaxCounterBits = sizeof(*Buffer.Data) * 8UL; 241 // Rounding counter storage size up to the power of two allows for using 242 // bit shifts calculating particular counter's Index and offset. 243 const uptr CounterSizeBits = 244 roundUpPowerOfTwo(getMostSignificantSetBitIndex(MaxValue) + 1); 245 DCHECK_LE(CounterSizeBits, MaxCounterBits); 246 CounterSizeBitsLog = getLog2(CounterSizeBits); 247 CounterMask = ~(static_cast<uptr>(0)) >> (MaxCounterBits - CounterSizeBits); 248 249 const uptr PackingRatio = MaxCounterBits >> CounterSizeBitsLog; 250 DCHECK_GT(PackingRatio, 0); 251 PackingRatioLog = getLog2(PackingRatio); 252 BitOffsetMask = PackingRatio - 1; 253 254 SizePerRegion = 255 roundUp(NumCounters, static_cast<uptr>(1U) << PackingRatioLog) >> 256 PackingRatioLog; 257 BufferNumElements = SizePerRegion * Regions; 258 Buffer = Buffers.getBuffer(BufferNumElements); 259 } 260 261 bool isAllocated() const { return Buffer.Data != nullptr; } 262 263 uptr getCount() const { return NumCounters; } 264 265 uptr get(uptr Region, uptr I) const { 266 DCHECK_LT(Region, Regions); 267 DCHECK_LT(I, NumCounters); 268 const uptr Index = I >> PackingRatioLog; 269 const uptr BitOffset = (I & BitOffsetMask) << CounterSizeBitsLog; 270 return (Buffer.Data[Region * SizePerRegion + Index] >> BitOffset) & 271 CounterMask; 272 } 273 274 void inc(uptr Region, uptr I) const { 275 DCHECK_LT(get(Region, I), CounterMask); 276 const uptr Index = I >> PackingRatioLog; 277 const uptr BitOffset = (I & BitOffsetMask) << CounterSizeBitsLog; 278 DCHECK_LT(BitOffset, SCUDO_WORDSIZE); 279 DCHECK_EQ(isAllCounted(Region, I), false); 280 Buffer.Data[Region * SizePerRegion + Index] += static_cast<uptr>(1U) 281 << BitOffset; 282 } 283 284 void incN(uptr Region, uptr I, uptr N) const { 285 DCHECK_GT(N, 0U); 286 DCHECK_LE(N, CounterMask); 287 DCHECK_LE(get(Region, I), CounterMask - N); 288 const uptr Index = I >> PackingRatioLog; 289 const uptr BitOffset = (I & BitOffsetMask) << CounterSizeBitsLog; 290 DCHECK_LT(BitOffset, SCUDO_WORDSIZE); 291 DCHECK_EQ(isAllCounted(Region, I), false); 292 Buffer.Data[Region * SizePerRegion + Index] += N << BitOffset; 293 } 294 295 void incRange(uptr Region, uptr From, uptr To) const { 296 DCHECK_LE(From, To); 297 const uptr Top = Min(To + 1, NumCounters); 298 for (uptr I = From; I < Top; I++) 299 inc(Region, I); 300 } 301 302 // Set the counter to the max value. Note that the max number of blocks in a 303 // page may vary. To provide an easier way to tell if all the blocks are 304 // counted for different pages, set to the same max value to denote the 305 // all-counted status. 306 void setAsAllCounted(uptr Region, uptr I) const { 307 DCHECK_LE(get(Region, I), CounterMask); 308 const uptr Index = I >> PackingRatioLog; 309 const uptr BitOffset = (I & BitOffsetMask) << CounterSizeBitsLog; 310 DCHECK_LT(BitOffset, SCUDO_WORDSIZE); 311 Buffer.Data[Region * SizePerRegion + Index] |= CounterMask << BitOffset; 312 } 313 void setAsAllCountedRange(uptr Region, uptr From, uptr To) const { 314 DCHECK_LE(From, To); 315 const uptr Top = Min(To + 1, NumCounters); 316 for (uptr I = From; I < Top; I++) 317 setAsAllCounted(Region, I); 318 } 319 320 bool updateAsAllCountedIf(uptr Region, uptr I, uptr MaxCount) { 321 const uptr Count = get(Region, I); 322 if (Count == CounterMask) 323 return true; 324 if (Count == MaxCount) { 325 setAsAllCounted(Region, I); 326 return true; 327 } 328 return false; 329 } 330 bool isAllCounted(uptr Region, uptr I) const { 331 return get(Region, I) == CounterMask; 332 } 333 334 uptr getBufferNumElements() const { return BufferNumElements; } 335 336 private: 337 // We may consider making this configurable if there are cases which may 338 // benefit from this. 339 static const uptr StaticBufferCount = 2U; 340 static const uptr StaticBufferNumElements = 512U; 341 using BufferPoolT = BufferPool<StaticBufferCount, StaticBufferNumElements>; 342 static BufferPoolT Buffers; 343 344 uptr Regions; 345 uptr NumCounters; 346 uptr CounterSizeBitsLog; 347 uptr CounterMask; 348 uptr PackingRatioLog; 349 uptr BitOffsetMask; 350 351 uptr SizePerRegion; 352 uptr BufferNumElements; 353 BufferPoolT::Buffer Buffer; 354 }; 355 356 template <class ReleaseRecorderT> class FreePagesRangeTracker { 357 public: 358 explicit FreePagesRangeTracker(ReleaseRecorderT &Recorder) 359 : Recorder(Recorder) {} 360 361 void processNextPage(bool Released) { 362 if (Released) { 363 if (!InRange) { 364 CurrentRangeStatePage = CurrentPage; 365 InRange = true; 366 } 367 } else { 368 closeOpenedRange(); 369 } 370 CurrentPage++; 371 } 372 373 void skipPages(uptr N) { 374 closeOpenedRange(); 375 CurrentPage += N; 376 } 377 378 void finish() { closeOpenedRange(); } 379 380 private: 381 void closeOpenedRange() { 382 if (InRange) { 383 const uptr PageSizeLog = getPageSizeLogCached(); 384 Recorder.releasePageRangeToOS((CurrentRangeStatePage << PageSizeLog), 385 (CurrentPage << PageSizeLog)); 386 InRange = false; 387 } 388 } 389 390 ReleaseRecorderT &Recorder; 391 bool InRange = false; 392 uptr CurrentPage = 0; 393 uptr CurrentRangeStatePage = 0; 394 }; 395 396 struct PageReleaseContext { 397 PageReleaseContext(uptr BlockSize, uptr NumberOfRegions, uptr ReleaseSize, 398 uptr ReleaseOffset = 0) 399 : BlockSize(BlockSize), NumberOfRegions(NumberOfRegions) { 400 const uptr PageSize = getPageSizeCached(); 401 if (BlockSize <= PageSize) { 402 if (PageSize % BlockSize == 0) { 403 // Same number of chunks per page, no cross overs. 404 FullPagesBlockCountMax = PageSize / BlockSize; 405 SameBlockCountPerPage = true; 406 } else if (BlockSize % (PageSize % BlockSize) == 0) { 407 // Some chunks are crossing page boundaries, which means that the page 408 // contains one or two partial chunks, but all pages contain the same 409 // number of chunks. 410 FullPagesBlockCountMax = PageSize / BlockSize + 1; 411 SameBlockCountPerPage = true; 412 } else { 413 // Some chunks are crossing page boundaries, which means that the page 414 // contains one or two partial chunks. 415 FullPagesBlockCountMax = PageSize / BlockSize + 2; 416 SameBlockCountPerPage = false; 417 } 418 } else { 419 if ((BlockSize & (PageSize - 1)) == 0) { 420 // One chunk covers multiple pages, no cross overs. 421 FullPagesBlockCountMax = 1; 422 SameBlockCountPerPage = true; 423 } else { 424 // One chunk covers multiple pages, Some chunks are crossing page 425 // boundaries. Some pages contain one chunk, some contain two. 426 FullPagesBlockCountMax = 2; 427 SameBlockCountPerPage = false; 428 } 429 } 430 431 // TODO: For multiple regions, it's more complicated to support partial 432 // region marking (which includes the complexity of how to handle the last 433 // block in a region). We may consider this after markFreeBlocks() accepts 434 // only free blocks from the same region. 435 if (NumberOfRegions != 1) 436 DCHECK_EQ(ReleaseOffset, 0U); 437 438 const uptr PageSizeLog = getPageSizeLogCached(); 439 PagesCount = roundUp(ReleaseSize, PageSize) >> PageSizeLog; 440 ReleasePageOffset = ReleaseOffset >> PageSizeLog; 441 } 442 443 // PageMap is lazily allocated when markFreeBlocks() is invoked. 444 bool hasBlockMarked() const { 445 return PageMap.isAllocated(); 446 } 447 448 bool ensurePageMapAllocated() { 449 if (PageMap.isAllocated()) 450 return true; 451 PageMap.reset(NumberOfRegions, PagesCount, FullPagesBlockCountMax); 452 // TODO: Log some message when we fail on PageMap allocation. 453 return PageMap.isAllocated(); 454 } 455 456 // Mark all the blocks in the given range [From, to). Instead of visiting all 457 // the blocks, we will just mark the page as all counted. Note the `From` and 458 // `To` has to be page aligned but with one exception, if `To` is equal to the 459 // RegionSize, it's not necessary to be aligned with page size. 460 bool markRangeAsAllCounted(uptr From, uptr To, uptr Base, 461 const uptr RegionIndex, const uptr RegionSize) { 462 const uptr PageSize = getPageSizeCached(); 463 DCHECK_LT(From, To); 464 DCHECK_LE(To, Base + RegionSize); 465 DCHECK_EQ(From % PageSize, 0U); 466 DCHECK_LE(To - From, RegionSize); 467 468 if (!ensurePageMapAllocated()) 469 return false; 470 471 uptr FromInRegion = From - Base; 472 uptr ToInRegion = To - Base; 473 uptr FirstBlockInRange = roundUpSlow(FromInRegion, BlockSize); 474 475 // The straddling block sits across entire range. 476 if (FirstBlockInRange >= ToInRegion) 477 return true; 478 479 // First block may not sit at the first pape in the range, move 480 // `FromInRegion` to the first block page. 481 FromInRegion = roundDown(FirstBlockInRange, PageSize); 482 483 // When The first block is not aligned to the range boundary, which means 484 // there is a block sitting acorss `From`, that looks like, 485 // 486 // From To 487 // V V 488 // +-----------------------------------------------+ 489 // +-----+-----+-----+-----+ 490 // | | | | | ... 491 // +-----+-----+-----+-----+ 492 // |- first page -||- second page -||- ... 493 // 494 // Therefore, we can't just mark the first page as all counted. Instead, we 495 // increment the number of blocks in the first page in the page map and 496 // then round up the `From` to the next page. 497 if (FirstBlockInRange != FromInRegion) { 498 DCHECK_GT(FromInRegion + PageSize, FirstBlockInRange); 499 uptr NumBlocksInFirstPage = 500 (FromInRegion + PageSize - FirstBlockInRange + BlockSize - 1) / 501 BlockSize; 502 PageMap.incN(RegionIndex, getPageIndex(FromInRegion), 503 NumBlocksInFirstPage); 504 FromInRegion = roundUp(FromInRegion + 1, PageSize); 505 } 506 507 uptr LastBlockInRange = roundDownSlow(ToInRegion - 1, BlockSize); 508 509 // Note that LastBlockInRange may be smaller than `FromInRegion` at this 510 // point because it may contain only one block in the range. 511 512 // When the last block sits across `To`, we can't just mark the pages 513 // occupied by the last block as all counted. Instead, we increment the 514 // counters of those pages by 1. The exception is that if it's the last 515 // block in the region, it's fine to mark those pages as all counted. 516 if (LastBlockInRange + BlockSize != RegionSize) { 517 DCHECK_EQ(ToInRegion % PageSize, 0U); 518 // The case below is like, 519 // 520 // From To 521 // V V 522 // +----------------------------------------+ 523 // +-----+-----+-----+-----+ 524 // | | | | | ... 525 // +-----+-----+-----+-----+ 526 // ... -||- last page -||- next page -| 527 // 528 // The last block is not aligned to `To`, we need to increment the 529 // counter of `next page` by 1. 530 if (LastBlockInRange + BlockSize != ToInRegion) { 531 PageMap.incRange(RegionIndex, getPageIndex(ToInRegion), 532 getPageIndex(LastBlockInRange + BlockSize - 1)); 533 } 534 } else { 535 ToInRegion = RegionSize; 536 } 537 538 // After handling the first page and the last block, it's safe to mark any 539 // page in between the range [From, To). 540 if (FromInRegion < ToInRegion) { 541 PageMap.setAsAllCountedRange(RegionIndex, getPageIndex(FromInRegion), 542 getPageIndex(ToInRegion - 1)); 543 } 544 545 return true; 546 } 547 548 template <class TransferBatchT, typename DecompactPtrT> 549 bool markFreeBlocksInRegion(const IntrusiveList<TransferBatchT> &FreeList, 550 DecompactPtrT DecompactPtr, const uptr Base, 551 const uptr RegionIndex, const uptr RegionSize, 552 bool MayContainLastBlockInRegion) { 553 if (!ensurePageMapAllocated()) 554 return false; 555 556 const uptr PageSize = getPageSizeCached(); 557 if (MayContainLastBlockInRegion) { 558 const uptr LastBlockInRegion = 559 ((RegionSize / BlockSize) - 1U) * BlockSize; 560 // The last block in a region may not use the entire page, we mark the 561 // following "pretend" memory block(s) as free in advance. 562 // 563 // Region Boundary 564 // v 565 // -----+-----------------------+ 566 // | Last Page | <- Rounded Region Boundary 567 // -----+-----------------------+ 568 // |-----||- trailing blocks -| 569 // ^ 570 // last block 571 const uptr RoundedRegionSize = roundUp(RegionSize, PageSize); 572 const uptr TrailingBlockBase = LastBlockInRegion + BlockSize; 573 // If the difference between `RoundedRegionSize` and 574 // `TrailingBlockBase` is larger than a page, that implies the reported 575 // `RegionSize` may not be accurate. 576 DCHECK_LT(RoundedRegionSize - TrailingBlockBase, PageSize); 577 578 // Only the last page touched by the last block needs to mark the trailing 579 // blocks. Note that if the last "pretend" block straddles the boundary, 580 // we still have to count it in so that the logic of counting the number 581 // of blocks on a page is consistent. 582 uptr NumTrailingBlocks = 583 (roundUpSlow(RoundedRegionSize - TrailingBlockBase, BlockSize) + 584 BlockSize - 1) / 585 BlockSize; 586 if (NumTrailingBlocks > 0) { 587 PageMap.incN(RegionIndex, getPageIndex(TrailingBlockBase), 588 NumTrailingBlocks); 589 } 590 } 591 592 // Iterate over free chunks and count how many free chunks affect each 593 // allocated page. 594 if (BlockSize <= PageSize && PageSize % BlockSize == 0) { 595 // Each chunk affects one page only. 596 for (const auto &It : FreeList) { 597 for (u16 I = 0; I < It.getCount(); I++) { 598 const uptr PInRegion = DecompactPtr(It.get(I)) - Base; 599 DCHECK_LT(PInRegion, RegionSize); 600 PageMap.inc(RegionIndex, getPageIndex(PInRegion)); 601 } 602 } 603 } else { 604 // In all other cases chunks might affect more than one page. 605 DCHECK_GE(RegionSize, BlockSize); 606 for (const auto &It : FreeList) { 607 for (u16 I = 0; I < It.getCount(); I++) { 608 const uptr PInRegion = DecompactPtr(It.get(I)) - Base; 609 PageMap.incRange(RegionIndex, getPageIndex(PInRegion), 610 getPageIndex(PInRegion + BlockSize - 1)); 611 } 612 } 613 } 614 615 return true; 616 } 617 618 uptr getPageIndex(uptr P) { 619 return (P >> getPageSizeLogCached()) - ReleasePageOffset; 620 } 621 uptr getReleaseOffset() { 622 return ReleasePageOffset << getPageSizeLogCached(); 623 } 624 625 uptr BlockSize; 626 uptr NumberOfRegions; 627 // For partial region marking, some pages in front are not needed to be 628 // counted. 629 uptr ReleasePageOffset; 630 uptr PagesCount; 631 uptr FullPagesBlockCountMax; 632 bool SameBlockCountPerPage; 633 RegionPageMap PageMap; 634 }; 635 636 // Try to release the page which doesn't have any in-used block, i.e., they are 637 // all free blocks. The `PageMap` will record the number of free blocks in each 638 // page. 639 template <class ReleaseRecorderT, typename SkipRegionT> 640 NOINLINE void 641 releaseFreeMemoryToOS(PageReleaseContext &Context, 642 ReleaseRecorderT &Recorder, SkipRegionT SkipRegion) { 643 const uptr PageSize = getPageSizeCached(); 644 const uptr BlockSize = Context.BlockSize; 645 const uptr PagesCount = Context.PagesCount; 646 const uptr NumberOfRegions = Context.NumberOfRegions; 647 const uptr ReleasePageOffset = Context.ReleasePageOffset; 648 const uptr FullPagesBlockCountMax = Context.FullPagesBlockCountMax; 649 const bool SameBlockCountPerPage = Context.SameBlockCountPerPage; 650 RegionPageMap &PageMap = Context.PageMap; 651 652 // Iterate over pages detecting ranges of pages with chunk Counters equal 653 // to the expected number of chunks for the particular page. 654 FreePagesRangeTracker<ReleaseRecorderT> RangeTracker(Recorder); 655 if (SameBlockCountPerPage) { 656 // Fast path, every page has the same number of chunks affecting it. 657 for (uptr I = 0; I < NumberOfRegions; I++) { 658 if (SkipRegion(I)) { 659 RangeTracker.skipPages(PagesCount); 660 continue; 661 } 662 for (uptr J = 0; J < PagesCount; J++) { 663 const bool CanRelease = 664 PageMap.updateAsAllCountedIf(I, J, FullPagesBlockCountMax); 665 RangeTracker.processNextPage(CanRelease); 666 } 667 } 668 } else { 669 // Slow path, go through the pages keeping count how many chunks affect 670 // each page. 671 const uptr Pn = BlockSize < PageSize ? PageSize / BlockSize : 1; 672 const uptr Pnc = Pn * BlockSize; 673 // The idea is to increment the current page pointer by the first chunk 674 // size, middle portion size (the portion of the page covered by chunks 675 // except the first and the last one) and then the last chunk size, adding 676 // up the number of chunks on the current page and checking on every step 677 // whether the page boundary was crossed. 678 for (uptr I = 0; I < NumberOfRegions; I++) { 679 if (SkipRegion(I)) { 680 RangeTracker.skipPages(PagesCount); 681 continue; 682 } 683 uptr PrevPageBoundary = 0; 684 uptr CurrentBoundary = 0; 685 if (ReleasePageOffset > 0) { 686 PrevPageBoundary = ReleasePageOffset << getPageSizeLogCached(); 687 CurrentBoundary = roundUpSlow(PrevPageBoundary, BlockSize); 688 } 689 for (uptr J = 0; J < PagesCount; J++) { 690 const uptr PageBoundary = PrevPageBoundary + PageSize; 691 uptr BlocksPerPage = Pn; 692 if (CurrentBoundary < PageBoundary) { 693 if (CurrentBoundary > PrevPageBoundary) 694 BlocksPerPage++; 695 CurrentBoundary += Pnc; 696 if (CurrentBoundary < PageBoundary) { 697 BlocksPerPage++; 698 CurrentBoundary += BlockSize; 699 } 700 } 701 PrevPageBoundary = PageBoundary; 702 const bool CanRelease = 703 PageMap.updateAsAllCountedIf(I, J, BlocksPerPage); 704 RangeTracker.processNextPage(CanRelease); 705 } 706 } 707 } 708 RangeTracker.finish(); 709 } 710 711 } // namespace scudo 712 713 #endif // SCUDO_RELEASE_H_ 714