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