Skip to content

Commit

Permalink
[scudo] Verify the size of free blocks in primary allocator
Browse files Browse the repository at this point in the history
When all the blocks (local caches are included) are freed, the size of
free blocks should be equal to `AllocatedUser`.

Reviewed By: cferris

Differential Revision: https://reviews.llvm.org/D152769
  • Loading branch information
ChiaHungDuan committed Jul 6, 2023
1 parent 05e2bc2 commit 531f90a
Show file tree
Hide file tree
Showing 3 changed files with 82 additions and 3 deletions.
36 changes: 35 additions & 1 deletion compiler-rt/lib/scudo/standalone/primary32.h
Original file line number Diff line number Diff line change
Expand Up @@ -109,12 +109,46 @@ template <typename Config> class SizeClassAllocator32 {
}

ScopedLock L(ByteMapMutex);
for (uptr I = MinRegionIndex; I < MaxRegionIndex; I++)
for (uptr I = MinRegionIndex; I <= MaxRegionIndex; I++)
if (PossibleRegions[I])
unmap(reinterpret_cast<void *>(I * RegionSize), RegionSize);
PossibleRegions.unmapTestOnly();
}

// When all blocks are freed, it has to be the same size as `AllocatedUser`.
void verifyAllBlocksAreReleasedTestOnly() {
// `BatchGroup` and `TransferBatch` also use the blocks from BatchClass.
uptr BatchClassUsedInFreeLists = 0;
for (uptr I = 0; I < NumClasses; I++) {
// We have to count BatchClassUsedInFreeLists in other regions first.
if (I == SizeClassMap::BatchClassId)
continue;
SizeClassInfo *Sci = getSizeClassInfo(I);
ScopedLock L1(Sci->Mutex);
uptr TotalBlocks = 0;
for (BatchGroup &BG : Sci->FreeListInfo.BlockList) {
// `BG::Batches` are `TransferBatches`. +1 for `BatchGroup`.
BatchClassUsedInFreeLists += BG.Batches.size() + 1;
for (const auto &It : BG.Batches)
TotalBlocks += It.getCount();
}

const uptr BlockSize = getSizeByClassId(I);
DCHECK_EQ(TotalBlocks, Sci->AllocatedUser / BlockSize);
}

SizeClassInfo *Sci = getSizeClassInfo(SizeClassMap::BatchClassId);
ScopedLock L1(Sci->Mutex);
uptr TotalBlocks = 0;
for (BatchGroup &BG : Sci->FreeListInfo.BlockList)
for (const auto &It : BG.Batches)
TotalBlocks += It.getCount();

const uptr BlockSize = getSizeByClassId(SizeClassMap::BatchClassId);
DCHECK_EQ(TotalBlocks + BatchClassUsedInFreeLists,
Sci->AllocatedUser / BlockSize);
}

CompactPtrT compactPtr(UNUSED uptr ClassId, uptr Ptr) const {
return static_cast<CompactPtrT>(Ptr);
}
Expand Down
35 changes: 35 additions & 0 deletions compiler-rt/lib/scudo/standalone/primary64.h
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,41 @@ template <typename Config> class SizeClassAllocator64 {
PrimaryBase = 0U;
}

// When all blocks are freed, it has to be the same size as `AllocatedUser`.
void verifyAllBlocksAreReleasedTestOnly() {
// `BatchGroup` and `TransferBatch` also use the blocks from BatchClass.
uptr BatchClassUsedInFreeLists = 0;
for (uptr I = 0; I < NumClasses; I++) {
// We have to count BatchClassUsedInFreeLists in other regions first.
if (I == SizeClassMap::BatchClassId)
continue;
RegionInfo *Region = getRegionInfo(I);
ScopedLock ML(Region->MMLock);
ScopedLock FL(Region->FLLock);
const uptr BlockSize = getSizeByClassId(I);
uptr TotalBlocks = 0;
for (BatchGroup &BG : Region->FreeListInfo.BlockList) {
// `BG::Batches` are `TransferBatches`. +1 for `BatchGroup`.
BatchClassUsedInFreeLists += BG.Batches.size() + 1;
for (const auto &It : BG.Batches)
TotalBlocks += It.getCount();
}

DCHECK_EQ(TotalBlocks, Region->MemMapInfo.AllocatedUser / BlockSize);
}

RegionInfo *Region = getRegionInfo(SizeClassMap::BatchClassId);
ScopedLock ML(Region->MMLock);
ScopedLock FL(Region->FLLock);
const uptr BlockSize = getSizeByClassId(SizeClassMap::BatchClassId);
uptr TotalBlocks = 0;
for (BatchGroup &BG : Region->FreeListInfo.BlockList)
for (const auto &It : BG.Batches)
TotalBlocks += It.getCount();
DCHECK_EQ(TotalBlocks + BatchClassUsedInFreeLists,
Region->MemMapInfo.AllocatedUser / BlockSize);
}

TransferBatch *popBatch(CacheT *C, uptr ClassId) {
DCHECK_LT(ClassId, NumClasses);
RegionInfo *Region = getRegionInfo(ClassId);
Expand Down
14 changes: 12 additions & 2 deletions compiler-rt/lib/scudo/standalone/tests/primary_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,10 @@ struct SizeClassAllocator<TestConfig1, SizeClassMapT>

template <template <typename> class BaseConfig, typename SizeClassMapT>
struct TestAllocator : public SizeClassAllocator<BaseConfig, SizeClassMapT> {
~TestAllocator() { this->unmapTestOnly(); }
~TestAllocator() {
this->verifyAllBlocksAreReleasedTestOnly();
this->unmapTestOnly();
}

void *operator new(size_t size) {
void *p = nullptr;
Expand Down Expand Up @@ -286,7 +289,7 @@ SCUDO_TYPED_TEST(ScudoPrimaryTest, PrimaryThreaded) {
std::condition_variable Cv;
bool Ready = false;
std::thread Threads[32];
for (scudo::uptr I = 0; I < ARRAY_SIZE(Threads); I++)
for (scudo::uptr I = 0; I < ARRAY_SIZE(Threads); I++) {
Threads[I] = std::thread([&]() {
static thread_local typename Primary::CacheT Cache;
Cache.init(nullptr, Allocator.get());
Expand All @@ -312,6 +315,7 @@ SCUDO_TYPED_TEST(ScudoPrimaryTest, PrimaryThreaded) {
}
Cache.destroy(nullptr);
});
}
{
std::unique_lock<std::mutex> Lock(Mutex);
Ready = true;
Expand Down Expand Up @@ -386,4 +390,10 @@ SCUDO_TYPED_TEST(ScudoPrimaryTest, MemoryGroup) {
EXPECT_LE(*std::max_element(Blocks.begin(), Blocks.end()) -
*std::min_element(Blocks.begin(), Blocks.end()),
GroupSizeMem * 2);

while (!Blocks.empty()) {
Cache.deallocate(ClassId, reinterpret_cast<void *>(Blocks.back()));
Blocks.pop_back();
}
Cache.drain();
}

0 comments on commit 531f90a

Please sign in to comment.