Skip to content

Commit 6fb70a8

Browse files
committed
[scudo] Fix missing one block in range marking
When a range contains only one block, we may not mark the pages touched by the block as can-be-released. This happens in the last group and if it only contains single block. Also enhance the existing tests and add a new test for testing the last block. Differential Revision: https://reviews.llvm.org/D149866
1 parent b77d6f5 commit 6fb70a8

File tree

2 files changed

+88
-17
lines changed

2 files changed

+88
-17
lines changed

compiler-rt/lib/scudo/standalone/release.h

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -480,8 +480,9 @@ struct PageReleaseContext {
480480
}
481481

482482
uptr LastBlockInRange = roundDownSlow(ToInRegion - 1, BlockSize);
483-
if (LastBlockInRange < FromInRegion)
484-
return;
483+
484+
// Note that LastBlockInRange may be smaller than `FromInRegion` at this
485+
// point because it may contain only one block in the range.
485486

486487
// When the last block sits across `To`, we can't just mark the pages
487488
// occupied by the last block as all counted. Instead, we increment the
@@ -540,13 +541,18 @@ struct PageReleaseContext {
540541
// last block
541542
const uptr RoundedRegionSize = roundUp(RegionSize, PageSize);
542543
const uptr TrailingBlockBase = LastBlockInRegion + BlockSize;
543-
// Only the last page touched by the last block needs to mark the trailing
544-
// blocks. If the difference between `RoundedRegionSize` and
544+
// If the difference between `RoundedRegionSize` and
545545
// `TrailingBlockBase` is larger than a page, that implies the reported
546546
// `RegionSize` may not be accurate.
547547
DCHECK_LT(RoundedRegionSize - TrailingBlockBase, PageSize);
548+
549+
// Only the last page touched by the last block needs to mark the trailing
550+
// blocks. Note that if the last "pretend" block straddles the boundary,
551+
// we still have to count it in so that the logic of counting the number
552+
// of blocks on a page is consistent.
548553
uptr NumTrailingBlocks =
549-
roundUpSlow(RoundedRegionSize - TrailingBlockBase, BlockSize) /
554+
(roundUpSlow(RoundedRegionSize - TrailingBlockBase, BlockSize) +
555+
BlockSize - 1) /
550556
BlockSize;
551557
if (NumTrailingBlocks > 0) {
552558
PageMap.incN(RegionIndex, getPageIndex(TrailingBlockBase),

compiler-rt/lib/scudo/standalone/tests/release_test.cpp

Lines changed: 77 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -315,12 +315,13 @@ template <class SizeClassMap> void testPageMapMarkRange() {
315315
const scudo::uptr RoundedRegionSize = scudo::roundUp(RegionSize, PageSize);
316316

317317
std::vector<scudo::uptr> Pages(RoundedRegionSize / PageSize, 0);
318-
for (scudo::uptr Block = 0; Block + BlockSize <= RoundedRegionSize;
319-
Block += BlockSize) {
320-
for (scudo::uptr page = Block / PageSize;
321-
page <= (Block + BlockSize - 1) / PageSize; ++page) {
322-
ASSERT_LT(page, Pages.size());
323-
++Pages[page];
318+
for (scudo::uptr Block = 0; Block < RoundedRegionSize; Block += BlockSize) {
319+
for (scudo::uptr Page = Block / PageSize;
320+
Page <= (Block + BlockSize - 1) / PageSize &&
321+
Page < RoundedRegionSize / PageSize;
322+
++Page) {
323+
ASSERT_LT(Page, Pages.size());
324+
++Pages[Page];
324325
}
325326
}
326327

@@ -410,17 +411,18 @@ template <class SizeClassMap> void testPageMapMarkRange() {
410411
template <class SizeClassMap> void testReleasePartialRegion() {
411412
typedef FreeBatch<SizeClassMap> Batch;
412413
const scudo::uptr PageSize = scudo::getPageSizeCached();
413-
const scudo::uptr ReleaseBase = PageSize;
414-
const scudo::uptr BasePageOffset = ReleaseBase / PageSize;
415414

416415
for (scudo::uptr I = 1; I <= SizeClassMap::LargestClassId; I++) {
417416
// In the following, we want to ensure the region includes at least 2 pages
418417
// and we will release all the pages except the first one. The handling of
419418
// the last block is tricky, so we always test the case that includes the
420419
// last block.
421420
const scudo::uptr BlockSize = SizeClassMap::getSizeByClassId(I);
421+
const scudo::uptr ReleaseBase = scudo::roundUp(BlockSize, PageSize);
422+
const scudo::uptr BasePageOffset = ReleaseBase / PageSize;
422423
const scudo::uptr RegionSize =
423-
scudo::roundUpSlow(scudo::roundUp(BlockSize, PageSize) * 2, BlockSize) +
424+
scudo::roundUpSlow(scudo::roundUp(BlockSize, PageSize) + ReleaseBase,
425+
BlockSize) +
424426
BlockSize;
425427
const scudo::uptr RoundedRegionSize = scudo::roundUp(RegionSize, PageSize);
426428

@@ -429,7 +431,7 @@ template <class SizeClassMap> void testReleasePartialRegion() {
429431

430432
// Skip the blocks in the first page and add the remaining.
431433
std::vector<scudo::uptr> Pages(RoundedRegionSize / PageSize, 0);
432-
for (scudo::uptr Block = scudo::roundUpSlow(PageSize, BlockSize);
434+
for (scudo::uptr Block = scudo::roundUpSlow(ReleaseBase, BlockSize);
433435
Block + BlockSize <= RoundedRegionSize; Block += BlockSize) {
434436
for (scudo::uptr Page = Block / PageSize;
435437
Page <= (Block + BlockSize - 1) / PageSize; ++Page) {
@@ -444,7 +446,7 @@ template <class SizeClassMap> void testReleasePartialRegion() {
444446
++Pages.back();
445447

446448
Batch *CurrentBatch = nullptr;
447-
for (scudo::uptr Block = scudo::roundUpSlow(PageSize, BlockSize);
449+
for (scudo::uptr Block = scudo::roundUpSlow(ReleaseBase, BlockSize);
448450
Block < RegionSize; Block += BlockSize) {
449451
if (CurrentBatch == nullptr ||
450452
CurrentBatch->getCount() == Batch::MaxCount) {
@@ -459,7 +461,7 @@ template <class SizeClassMap> void testReleasePartialRegion() {
459461
auto SkipRegion = [](UNUSED scudo::uptr RegionIndex) { return false; };
460462
ReleasedPagesRecorder Recorder(ReleaseBase);
461463
releaseFreeMemoryToOS(Context, Recorder, SkipRegion);
462-
const scudo::uptr FirstBlock = scudo::roundUpSlow(PageSize, BlockSize);
464+
const scudo::uptr FirstBlock = scudo::roundUpSlow(ReleaseBase, BlockSize);
463465

464466
for (scudo::uptr P = 0; P < RoundedRegionSize; P += PageSize) {
465467
if (P < FirstBlock) {
@@ -563,6 +565,69 @@ TEST(ScudoReleaseTest, ReleasePartialRegion) {
563565
testReleasePartialRegion<scudo::SvelteSizeClassMap>();
564566
}
565567

568+
template <class SizeClassMap> void testReleaseRangeWithSingleBlock() {
569+
const scudo::uptr PageSize = scudo::getPageSizeCached();
570+
571+
// We want to test if a memory group only contains single block that will be
572+
// handled properly. The case is like:
573+
//
574+
// From To
575+
// +----------------------+
576+
// +------------+------------+
577+
// | | |
578+
// +------------+------------+
579+
// ^
580+
// RegionSize
581+
//
582+
// Note that `From` will be page aligned.
583+
//
584+
// If the second from the last block is aligned at `From`, then we expect all
585+
// the pages after `From` will be marked as can-be-released. Otherwise, the
586+
// pages only touched by the last blocks will be marked as can-be-released.
587+
for (scudo::uptr I = 1; I <= SizeClassMap::LargestClassId; I++) {
588+
const scudo::uptr BlockSize = SizeClassMap::getSizeByClassId(I);
589+
const scudo::uptr From = scudo::roundUp(BlockSize, PageSize);
590+
const scudo::uptr To =
591+
From % BlockSize == 0
592+
? From + BlockSize
593+
: scudo::roundDownSlow(From + BlockSize, BlockSize) + BlockSize;
594+
const scudo::uptr RoundedRegionSize = scudo::roundUp(To, PageSize);
595+
596+
std::vector<scudo::uptr> Pages(RoundedRegionSize / PageSize, 0);
597+
for (scudo::uptr Block = (To - BlockSize); Block < RoundedRegionSize;
598+
Block += BlockSize) {
599+
for (scudo::uptr Page = Block / PageSize;
600+
Page <= (Block + BlockSize - 1) / PageSize &&
601+
Page < RoundedRegionSize / PageSize;
602+
++Page) {
603+
ASSERT_LT(Page, Pages.size());
604+
++Pages[Page];
605+
}
606+
}
607+
608+
scudo::PageReleaseContext Context(BlockSize, /*NumberOfRegions=*/1U,
609+
/*ReleaseSize=*/To,
610+
/*ReleaseBase=*/0U);
611+
Context.markRangeAsAllCounted(From, To, /*Base=*/0U, /*RegionIndex=*/0,
612+
/*RegionSize=*/To);
613+
614+
for (scudo::uptr Page = 0; Page < RoundedRegionSize; Page += PageSize) {
615+
if (Context.PageMap.get(/*Region=*/0U, Page / PageSize) !=
616+
Pages[Page / PageSize]) {
617+
EXPECT_TRUE(
618+
Context.PageMap.isAllCounted(/*Region=*/0U, Page / PageSize));
619+
}
620+
}
621+
} // for each size class
622+
}
623+
624+
TEST(ScudoReleaseTest, RangeReleaseRegionWithSingleBlock) {
625+
testReleaseRangeWithSingleBlock<scudo::DefaultSizeClassMap>();
626+
testReleaseRangeWithSingleBlock<scudo::AndroidSizeClassMap>();
627+
testReleaseRangeWithSingleBlock<scudo::FuchsiaSizeClassMap>();
628+
testReleaseRangeWithSingleBlock<scudo::SvelteSizeClassMap>();
629+
}
630+
566631
TEST(ScudoReleaseTest, BufferPool) {
567632
constexpr scudo::uptr StaticBufferCount = SCUDO_WORDSIZE - 1;
568633
constexpr scudo::uptr StaticBufferSize = 512U;

0 commit comments

Comments
 (0)