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