blob: f87e7bd21530de24f35061b92a75b8f70b966bd7 [file] [log] [blame]
//-------------------------------------------------------------------------------------------------------
// Copyright (C) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE.txt file in the project root for full license information.
//-------------------------------------------------------------------------------------------------------
#include "CommonMemoryPch.h"
HeapBucket::HeapBucket() :
heapInfo(nullptr),
sizeCat(0)
{
#if defined(RECYCLER_SLOW_CHECK_ENABLED) || ENABLE_ALLOCATIONS_DURING_CONCURRENT_SWEEP
heapBlockCount = 0;
newHeapBlockCount = 0;
#endif
#if defined(RECYCLER_SLOW_CHECK_ENABLED)
emptyHeapBlockCount = 0;
#endif
#if ENABLE_ALLOCATIONS_DURING_CONCURRENT_SWEEP
this->allocationsStartedDuringConcurrentSweep = false;
this->concurrentSweepAllocationsThresholdExceeded = false;
#endif
#ifdef RECYCLER_PAGE_HEAP
isPageHeapEnabled = false;
#endif
}
HeapInfo *
HeapBucket::GetHeapInfo() const
{
return this->heapInfo;
}
uint
HeapBucket::GetSizeCat() const
{
return this->sizeCat;
}
uint
HeapBucket::GetBucketIndex() const
{
return HeapInfo::GetBucketIndex(this->sizeCat);
}
uint
HeapBucket::GetMediumBucketIndex() const
{
return HeapInfo::GetMediumBucketIndex(this->sizeCat);
}
namespace Memory
{
template <typename TBlockType>
HeapBucketT<TBlockType>::HeapBucketT() :
nextAllocableBlockHead(nullptr),
emptyBlockList(nullptr),
fullBlockList(nullptr),
heapBlockList(nullptr),
#if ENABLE_ALLOCATIONS_DURING_CONCURRENT_SWEEP
#if SUPPORT_WIN32_SLIST
lastKnownNextAllocableBlockHead(nullptr),
allocableHeapBlockListHead(nullptr),
sweepableHeapBlockList(nullptr),
#endif
#endif
explicitFreeList(nullptr),
lastExplicitFreeListAllocator(nullptr)
{
#ifdef RECYCLER_PAGE_HEAP
explicitFreeLockBlockList = nullptr;
#endif
isAllocationStopped = false;
}
template <typename TBlockType>
HeapBucketT<TBlockType>::~HeapBucketT()
{
DeleteHeapBlockList(this->heapBlockList);
DeleteHeapBlockList(this->fullBlockList);
#if ENABLE_ALLOCATIONS_DURING_CONCURRENT_SWEEP
#if SUPPORT_WIN32_SLIST
if (allocableHeapBlockListHead != nullptr)
{
if (CONFIG_FLAG_RELEASE(EnableConcurrentSweepAlloc))
{
FlushInterlockedSList(this->allocableHeapBlockListHead);
}
_aligned_free(this->allocableHeapBlockListHead);
}
DeleteHeapBlockList(this->sweepableHeapBlockList);
#endif
#endif
#if defined(RECYCLER_SLOW_CHECK_ENABLED) || ENABLE_ALLOCATIONS_DURING_CONCURRENT_SWEEP
Assert(this->heapBlockCount + this->newHeapBlockCount == 0);
#endif
RECYCLER_SLOW_CHECK(Assert(this->emptyHeapBlockCount == HeapBlockList::Count(this->emptyBlockList)));
DeleteEmptyHeapBlockList(this->emptyBlockList);
#if defined(RECYCLER_SLOW_CHECK_ENABLED)
Assert(this->heapBlockCount + this->newHeapBlockCount + this->emptyHeapBlockCount == 0);
#endif
}
};
template <typename TBlockType>
void
HeapBucketT<TBlockType>::DeleteHeapBlockList(TBlockType * list, Recycler * recycler)
{
HeapBlockList::ForEachEditing(list, [recycler](TBlockType * heapBlock)
{
#if DBG
heapBlock->ReleasePagesShutdown(recycler);
#endif
TBlockType::Delete(heapBlock);
});
}
template <typename TBlockType>
void
HeapBucketT<TBlockType>::DeleteEmptyHeapBlockList(TBlockType * list)
{
HeapBlockList::ForEachEditing(list, [](TBlockType * heapBlock)
{
TBlockType::Delete(heapBlock);
});
}
template <typename TBlockType>
void
HeapBucketT<TBlockType>::DeleteHeapBlockList(TBlockType * list)
{
DeleteHeapBlockList(list, this->heapInfo->recycler);
}
#if ENABLE_ALLOCATIONS_DURING_CONCURRENT_SWEEP && SUPPORT_WIN32_SLIST
template<typename TBlockType>
bool
HeapBucketT<TBlockType>::PushHeapBlockToSList(PSLIST_HEADER list, TBlockType * heapBlock)
{
Assert(list != nullptr);
HeapBlockSListItem<TBlockType> * currentBlock = (HeapBlockSListItem<TBlockType> *) _aligned_malloc(sizeof(HeapBlockSListItem<TBlockType>), MEMORY_ALLOCATION_ALIGNMENT);
if (currentBlock == nullptr)
{
return false;
}
// While in the SLIST the blocks live as standalone, when they come out they
// will go into appropriate list and the Next block will be set accordingly.
heapBlock->SetNextBlock(nullptr);
currentBlock->itemHeapBlock = heapBlock;
::InterlockedPushEntrySList(list, &(currentBlock->itemEntry));
return true;
}
template<typename TBlockType>
TBlockType *
HeapBucketT<TBlockType>::PopHeapBlockFromSList(PSLIST_HEADER list)
{
Assert(list != nullptr);
TBlockType * heapBlock = nullptr;
PSLIST_ENTRY top = ::InterlockedPopEntrySList(list);
if (top != nullptr)
{
HeapBlockSListItem<TBlockType> * topItem = (HeapBlockSListItem<TBlockType> *) top;
heapBlock = topItem->itemHeapBlock;
Assert(heapBlock != nullptr);
_aligned_free(top);
}
return heapBlock;
}
template<typename TBlockType>
ushort
HeapBucketT<TBlockType>::QueryDepthInterlockedSList(PSLIST_HEADER list)
{
Assert(list != nullptr);
return ::QueryDepthSList(list);
}
template<typename TBlockType>
void
HeapBucketT<TBlockType>::FlushInterlockedSList(PSLIST_HEADER list)
{
Assert(list != nullptr);
if (::QueryDepthSList(list) > 0)
{
PSLIST_ENTRY listEntry = ::InterlockedPopEntrySList(list);
while (listEntry != nullptr)
{
_aligned_free(listEntry);
listEntry = ::InterlockedPopEntrySList(list);
}
}
::InterlockedFlushSList(list);
}
#endif
template <typename TBlockType>
void
HeapBucketT<TBlockType>::Initialize(HeapInfo * heapInfo, uint sizeCat)
{
this->heapInfo = heapInfo;
#ifdef RECYCLER_PAGE_HEAP
this->isPageHeapEnabled = heapInfo->IsPageHeapEnabledForBlock<typename TBlockType::HeapBlockAttributes>(sizeCat);
#endif
this->sizeCat = sizeCat;
allocatorHead.Initialize();
#if defined(PROFILE_RECYCLER_ALLOC) || defined(RECYCLER_MEMORY_VERIFY)
allocatorHead.bucket = this;
#endif
this->lastExplicitFreeListAllocator = &allocatorHead;
}
template <typename TBlockType>
template <class Fn>
void
HeapBucketT<TBlockType>::ForEachAllocator(Fn fn)
{
TBlockAllocatorType * current = &allocatorHead;
do
{
fn(current);
current = current->GetNext();
}
while (current != &allocatorHead);
}
template <typename TBlockType>
void
HeapBucketT<TBlockType>::UpdateAllocators()
{
ForEachAllocator([](TBlockAllocatorType * allocator) { allocator->UpdateHeapBlock(); });
}
template <typename TBlockType>
void
HeapBucketT<TBlockType>::ClearAllocators()
{
ForEachAllocator([](TBlockAllocatorType * allocator) { ClearAllocator(allocator); });
#ifdef RECYCLER_PAGE_HEAP
#endif
#ifdef RECYCLER_MEMORY_VERIFY
FreeObject* freeObject = this->explicitFreeList;
while (freeObject)
{
HeapBlock* heapBlock = this->GetRecycler()->FindHeapBlock((void*)freeObject);
Assert(heapBlock != nullptr);
Assert(!heapBlock->IsLargeHeapBlock());
TBlockType* smallBlock = (TBlockType*)heapBlock;
smallBlock->ClearExplicitFreeBitForObject((void*)freeObject);
freeObject = freeObject->GetNext();
}
#endif
this->explicitFreeList = nullptr;
}
#if ENABLE_CONCURRENT_GC
template <typename TBlockType>
void
HeapBucketT<TBlockType>::PrepareSweep()
{
// CONCURRENT-TODO: Technically, We don't really need to invalidate allocators here,
// but currently invalidating may update the unallocateCount which is
// used to calculate the partial heuristics, so it needs to be done
// before sweep. When the partial heuristic changes, we can remove this
// (And remove rescan from leaf bucket, so this function doesn't need to exist)
ClearAllocators();
}
#endif
template <typename TBlockType>
void
HeapBucketT<TBlockType>::AddAllocator(TBlockAllocatorType * allocator)
{
Assert(allocator != &this->allocatorHead);
allocator->Initialize();
allocator->next = this->allocatorHead.next;
allocator->prev = &this->allocatorHead;
allocator->next->prev = allocator;
this->allocatorHead.next = allocator;
#if defined(PROFILE_RECYCLER_ALLOC) || defined(RECYCLER_MEMORY_VERIFY)
allocator->bucket = this;
#endif
}
template <typename TBlockType>
void
HeapBucketT<TBlockType>::RemoveAllocator(TBlockAllocatorType * allocator)
{
Assert(allocator != &this->allocatorHead);
ClearAllocator(allocator);
allocator->next->prev = allocator->prev;
allocator->prev->next = allocator->next;
if (allocator == this->lastExplicitFreeListAllocator)
{
this->lastExplicitFreeListAllocator = &allocatorHead;
}
}
template <typename TBlockType>
void
HeapBucketT<TBlockType>::ClearAllocator(TBlockAllocatorType * allocator)
{
allocator->Clear();
}
template <typename TBlockType>
bool
HeapBucketT<TBlockType>::IntegrateBlock(char * blockAddress, PageSegment * segment, Recycler * recycler)
{
// Add a new heap block
TBlockType * heapBlock = GetUnusedHeapBlock();
if (heapBlock == nullptr)
{
return false;
}
// TODO: Consider supporting guard pages for this codepath
if (!heapBlock->SetPage(blockAddress, segment, recycler))
{
FreeHeapBlock(heapBlock);
return false;
}
heapBlock->SetNextBlock(this->fullBlockList);
this->fullBlockList = heapBlock;
#if defined(RECYCLER_SLOW_CHECK_ENABLED) || ENABLE_ALLOCATIONS_DURING_CONCURRENT_SWEEP
this->heapBlockCount++;
#endif
recycler->autoHeap.uncollectedAllocBytes += heapBlock->GetAndClearLastFreeCount() * heapBlock->GetObjectSize();
RecyclerMemoryTracking::ReportAllocation(recycler, blockAddress, heapBlock->GetObjectSize() * heapBlock->GetObjectCount());
RECYCLER_PERF_COUNTER_ADD(LiveObject,heapBlock->GetObjectCount());
RECYCLER_PERF_COUNTER_ADD(LiveObjectSize, heapBlock->GetObjectSize() * heapBlock->GetObjectCount());
if (heapBlock->IsLargeHeapBlock())
{
RECYCLER_PERF_COUNTER_ADD(LargeHeapBlockLiveObject,heapBlock->GetObjectCount());
RECYCLER_PERF_COUNTER_ADD(LargeHeapBlockLiveObjectSize, heapBlock->GetObjectSize() * heapBlock->GetObjectCount());
}
else
{
RECYCLER_PERF_COUNTER_ADD(SmallHeapBlockLiveObject,heapBlock->GetObjectCount());
RECYCLER_PERF_COUNTER_ADD(SmallHeapBlockLiveObjectSize, heapBlock->GetObjectSize() * heapBlock->GetObjectCount());
}
#if DBG
heapBlock->SetIsIntegratedBlock();
#endif
return true;
}
#if DBG
template <typename TBlockType>
bool
HeapBucketT<TBlockType>::AllocatorsAreEmpty() const
{
TBlockAllocatorType const * current = &allocatorHead;
do
{
if (current->GetHeapBlock() != nullptr || current->GetExplicitFreeList() != nullptr)
{
return false;
}
current = current->GetNext();
}
while (current != &allocatorHead);
return true;
}
template <typename TBlockType>
bool
HeapBucketT<TBlockType>::HasPendingDisposeHeapBlocks() const
{
#ifdef RECYCLER_WRITE_BARRIER
return (IsFinalizableBucket || IsFinalizableWriteBarrierBucket) &&
((SmallFinalizableHeapBucketT<typename TBlockType::HeapBlockAttributes> *)this)->pendingDisposeList != nullptr;
#else
return IsFinalizableBucket && ((SmallFinalizableHeapBucketT<typename TBlockType::HeapBlockAttributes> *)this)->pendingDisposeList != nullptr;
#endif
}
#endif
template <typename TBlockType>
void
HeapBucketT<TBlockType>::AssertCheckHeapBlockNotInAnyList(TBlockType * heapBlock)
{
#if DBG
AssertMsg(!HeapBlockList::Contains(heapBlock, heapBlockList), "The heap block already exists in the heapBlockList.");
AssertMsg(!HeapBlockList::Contains(heapBlock, fullBlockList), "The heap block already exists in the fullBlockList.");
AssertMsg(!HeapBlockList::Contains(heapBlock, emptyBlockList), "The heap block already exists in the emptyBlockList.");
#if ENABLE_ALLOCATIONS_DURING_CONCURRENT_SWEEP
AssertMsg(!HeapBlockList::Contains(heapBlock, sweepableHeapBlockList), "The heap block already exists in the sweepableHeapBlockList.");
#endif
#endif
}
#if DBG || defined(RECYCLER_SLOW_CHECK_ENABLED)
template <typename TBlockType>
size_t
HeapBucketT<TBlockType>::GetNonEmptyHeapBlockCount(bool checkCount) const
{
size_t currentHeapBlockCount = HeapBlockList::Count(fullBlockList);
currentHeapBlockCount += HeapBlockList::Count(heapBlockList);
bool allocatingDuringConcurrentSweep = false;
#if ENABLE_CONCURRENT_GC
#if ENABLE_ALLOCATIONS_DURING_CONCURRENT_SWEEP
#if SUPPORT_WIN32_SLIST
if (CONFIG_FLAG_RELEASE(EnableConcurrentSweepAlloc) && !this->IsAnyFinalizableBucket())
{
allocatingDuringConcurrentSweep = true;
// This lock is needed only in the debug mode while we verify block counts. Not needed otherwise, as this list is never accessed concurrently.
// Items are added to it by the allocator when allocations are allowed during concurrent sweep. The list is drained during the next sweep while
// allocation are stopped.
debugSweepableHeapBlockListLock.Enter();
if (allocableHeapBlockListHead != nullptr)
{
currentHeapBlockCount += QueryDepthInterlockedSList(allocableHeapBlockListHead);
}
currentHeapBlockCount += HeapBlockList::Count(sweepableHeapBlockList);
debugSweepableHeapBlockListLock.Leave();
}
#endif
#endif
// Recycler can be null if we have OOM in the ctor
if (this->GetRecycler() && this->GetRecycler()->recyclerSweepManager != nullptr)
{
currentHeapBlockCount += this->GetRecycler()->recyclerSweepManager->GetHeapBlockCount(this);
}
#endif
// There is no way to determine the number of item in an SLIST if there are >= 65535 items in the list.
RECYCLER_SLOW_CHECK(Assert(!checkCount || heapBlockCount == currentHeapBlockCount || (heapBlockCount >= 65535 && allocatingDuringConcurrentSweep)));
return currentHeapBlockCount;
}
template <typename TBlockType>
size_t
HeapBucketT<TBlockType>::GetEmptyHeapBlockCount() const
{
size_t count = HeapBlockList::Count(this->emptyBlockList);
RECYCLER_SLOW_CHECK(Assert(count == this->emptyHeapBlockCount));
return count;
}
#endif
template <typename TBlockType>
char *
HeapBucketT<TBlockType>::TryAlloc(Recycler * recycler, TBlockAllocatorType * allocator, size_t sizeCat, ObjectInfoBits attributes)
{
AUTO_NO_EXCEPTION_REGION;
Assert((attributes & InternalObjectInfoBitMask) == attributes);
ClearAllocator(allocator);
TBlockType * heapBlock = this->nextAllocableBlockHead;
#if ENABLE_ALLOCATIONS_DURING_CONCURRENT_SWEEP
#if SUPPORT_WIN32_SLIST
bool heapBlockFromAllocableHeapBlockList = false;
DebugOnly(bool heapBlockInPendingSweepPrepList = false);
if (heapBlock == nullptr && this->allocationsStartedDuringConcurrentSweep)
{
#if DBG || defined(RECYCLER_SLOW_CHECK_ENABLED)
// This lock is needed only in the debug mode while we verify block counts. Not needed otherwise, as this list is never accessed concurrently.
// Items are added to it by the allocator when allocations are allowed during concurrent sweep. The list is drained during the next sweep while
// allocation are stopped.
debugSweepableHeapBlockListLock.Enter();
#endif
heapBlock = PopHeapBlockFromSList(this->allocableHeapBlockListHead);
if (heapBlock != nullptr)
{
Assert(!this->IsAnyFinalizableBucket());
DebugOnly(this->AssertCheckHeapBlockNotInAnyList(heapBlock));
#if DBG || defined(RECYCLER_SLOW_CHECK_ENABLED)
heapBlock->wasAllocatedFromDuringSweep = true;
#endif
#if DBG || defined(RECYCLER_TRACE)
if (heapBlock->isPendingConcurrentSweepPrep)
{
AssertMsg(heapBlock->objectsAllocatedDuringConcurrentSweepCount == 0, "We just picked up this block for allocations during concurrent sweep, we haven't allocated from it yet.");
#ifdef RECYCLER_TRACE
recycler->PrintBlockStatus(this, heapBlock, _u("[**31**] pending Pass1 prep, picked up for allocations during concurrent sweep."));
#endif
DebugOnly(heapBlockInPendingSweepPrepList = true);
}
else
{
// Put the block in the sweepable heap block list so we don't lose track of it. The block will eventually be moved to the
// heapBlockList or fullBlockList as appropriate during the next sweep.
AssertMsg(!HeapBlockList::Contains(heapBlock, sweepableHeapBlockList), "The heap block already exists in this list.");
#ifdef RECYCLER_TRACE
recycler->PrintBlockStatus(this, heapBlock, _u("[**32**] picked up for allocations during concurrent sweep."));
#endif
}
#endif
heapBlock->SetNextBlock(sweepableHeapBlockList);
sweepableHeapBlockList = heapBlock;
heapBlockFromAllocableHeapBlockList = true;
}
#if DBG|| defined(RECYCLER_SLOW_CHECK_ENABLED)
debugSweepableHeapBlockListLock.Leave();
#endif
}
#endif
#endif
if (heapBlock != nullptr)
{
Assert(!this->IsAllocationStopped());
#if ENABLE_ALLOCATIONS_DURING_CONCURRENT_SWEEP && SUPPORT_WIN32_SLIST
// When allocations are allowed during concurrent sweep we set nextAllocableBlockHead to NULL as the allocator will pick heap blocks from the
// interlocked SLIST. During that time, the heap block at the top of the SLIST is always the nextAllocableBlockHead.
// If the heapBlock was just picked from the SLIST and nextAllocableBlockHead is not NULL then we just resumed normal allocations on the background thread
// while finishing the concurrent sweep, and the nextAllocableBlockHead is already set properly.
if (this->nextAllocableBlockHead != nullptr && !heapBlockFromAllocableHeapBlockList)
#endif
{
this->nextAllocableBlockHead = heapBlock->GetNextBlock();
}
allocator->Set(heapBlock);
}
else if (this->explicitFreeList != nullptr)
{
allocator->SetExplicitFreeList(this->explicitFreeList);
this->lastExplicitFreeListAllocator = allocator;
this->explicitFreeList = nullptr;
}
else
{
return nullptr;
}
// We just found a block we can allocate on
char * memBlock = allocator->template SlowAlloc<false /* disallow fault injection */>(recycler, sizeCat, attributes);
Assert(memBlock != nullptr);
return memBlock;
}
template <typename TBlockType>
char *
HeapBucketT<TBlockType>::TryAllocFromNewHeapBlock(Recycler * recycler, TBlockAllocatorType * allocator, size_t sizeCat, size_t size, ObjectInfoBits attributes)
{
AUTO_NO_EXCEPTION_REGION;
Assert((attributes & InternalObjectInfoBitMask) == attributes);
#ifdef RECYCLER_PAGE_HEAP
if (IsPageHeapEnabled(attributes))
{
return this->PageHeapAlloc(recycler, sizeCat, size, attributes, this->heapInfo->pageHeapMode, true);
}
#endif
TBlockType * heapBlock = CreateHeapBlock(recycler);
if (heapBlock == nullptr)
{
return nullptr;
}
// new heap block added, allocate from that.
allocator->SetNew(heapBlock);
// We just created a block we can allocate on
char * memBlock = allocator->template SlowAlloc<false /* disallow fault injection */>(recycler, sizeCat, attributes);
Assert(memBlock != nullptr || IS_FAULTINJECT_NO_THROW_ON);
return memBlock;
}
Recycler *
HeapBucket::GetRecycler() const
{
return this->heapInfo->recycler;
}
bool
HeapBucket::AllocationsStartedDuringConcurrentSweep() const
{
#if ENABLE_ALLOCATIONS_DURING_CONCURRENT_SWEEP
return this->allocationsStartedDuringConcurrentSweep;
#else
return false;
#endif
}
#if ENABLE_ALLOCATIONS_DURING_CONCURRENT_SWEEP
bool
HeapBucket::ConcurrentSweepAllocationsThresholdExceeded() const
{
return this->concurrentSweepAllocationsThresholdExceeded;
}
bool
HeapBucket::DoTwoPassConcurrentSweepPreCheck()
{
this->concurrentSweepAllocationsThresholdExceeded = ((this->heapBlockCount + this->newHeapBlockCount) > RecyclerHeuristic::AllocDuringConcurrentSweepHeapBlockThreshold);
#ifdef RECYCLER_TRACE
if (this->GetRecycler()->GetRecyclerFlagsTable().Trace.IsEnabled(Js::ConcurrentSweepPhase) && CONFIG_FLAG_RELEASE(Verbose))
{
if (this->concurrentSweepAllocationsThresholdExceeded)
{
Output::Print(_u("[HeapBucket 0x%p] exceeded concurrent sweep allocations threshold (%d). Total heap block count: %d \n"), this, RecyclerHeuristic::AllocDuringConcurrentSweepHeapBlockThreshold, this->heapBlockCount + this->newHeapBlockCount);
}
}
#endif
return this->concurrentSweepAllocationsThresholdExceeded;
}
#endif
#ifdef RECYCLER_PAGE_HEAP
template <typename TBlockType>
char *
HeapBucketT<TBlockType>::PageHeapAlloc(Recycler * recycler, DECLSPEC_GUARD_OVERFLOW size_t sizeCat, size_t size, ObjectInfoBits attributes, PageHeapMode mode, bool nothrow)
{
AllocationVerboseTrace(recycler->GetRecyclerFlagsTable(), _u("In PageHeapAlloc [Size: 0x%x, Attributes: 0x%x]\n"), size, attributes);
char* addr = heapInfo->largeObjectBucket.PageHeapAlloc(recycler, sizeCat, size, attributes, mode, nothrow);
if (addr)
{
this->GetRecycler()->autoHeap.uncollectedAllocBytes += sizeCat;
}
return addr;
}
#endif
template <typename TBlockType>
char *
HeapBucketT<TBlockType>::SnailAlloc(Recycler * recycler, TBlockAllocatorType * allocator, DECLSPEC_GUARD_OVERFLOW size_t sizeCat, size_t size, ObjectInfoBits attributes, bool nothrow)
{
AllocationVerboseTrace(recycler->GetRecyclerFlagsTable(), _u("In SnailAlloc [Size: 0x%x, Attributes: 0x%x]\n"), sizeCat, attributes);
Assert(sizeCat == this->sizeCat);
Assert((attributes & InternalObjectInfoBitMask) == attributes);
char * memBlock = this->TryAlloc(recycler, allocator, sizeCat, attributes);
if (memBlock != nullptr)
{
return memBlock;
}
#if ENABLE_CONCURRENT_GC
// No free memory, try to collect with allocated bytes and time heuristic, and concurrently
BOOL collected = recycler->disableCollectOnAllocationHeuristics ? recycler->FinishConcurrent<FinishConcurrentOnAllocation>() :
recycler->CollectNow<CollectOnAllocation>();
#else
BOOL collected = recycler->disableCollectOnAllocationHeuristics ? FALSE : recycler->CollectNow<CollectOnAllocation>();
#endif
AllocationVerboseTrace(recycler->GetRecyclerFlagsTable(), _u("TryAlloc failed, forced collection on allocation [Collected: %d]\n"), collected);
if (!collected)
{
#if ENABLE_CONCURRENT_GC
#if ENABLE_PARTIAL_GC
// wait for background sweeping finish if there are too many pages allocated during background sweeping
if (recycler->IsConcurrentSweepExecutingState() && recycler->autoHeap.uncollectedNewPageCount > (uint)CONFIG_FLAG(NewPagesCapDuringBGSweeping))
#else
if (recycler->IsConcurrentSweepExecutingState())
#endif
{
recycler->FinishConcurrent<ForceFinishCollection>();
memBlock = this->TryAlloc(recycler, allocator, sizeCat, attributes);
if (memBlock != nullptr)
{
return memBlock;
}
}
#endif
// We didn't collect, try to add a new heap block
memBlock = TryAllocFromNewHeapBlock(recycler, allocator, sizeCat, size, attributes);
if (memBlock != nullptr)
{
return memBlock;
}
// Can't even allocate a new block, we need force a collection and
//allocate some free memory, add a new heap block again, or throw out of memory
AllocationVerboseTrace(recycler->GetRecyclerFlagsTable(), _u("TryAllocFromNewHeapBlock failed, forcing in-thread collection\n"));
recycler->CollectNow<CollectNowForceInThread>();
}
// Collection might trigger finalizer, which might allocate memory. So the allocator
// might have a heap block already, try to allocate from that first
memBlock = allocator->template SlowAlloc<true /* allow fault injection */>(recycler, sizeCat, attributes);
if (memBlock != nullptr)
{
return memBlock;
}
AllocationVerboseTrace(recycler->GetRecyclerFlagsTable(), _u("SlowAlloc failed\n"));
// do the allocation
memBlock = this->TryAlloc(recycler, allocator, sizeCat, attributes);
if (memBlock != nullptr)
{
return memBlock;
}
AllocationVerboseTrace(recycler->GetRecyclerFlagsTable(), _u("TryAlloc failed\n"));
// add a heap block if there are no preallocated memory left.
memBlock = TryAllocFromNewHeapBlock(recycler, allocator, sizeCat, size, attributes);
if (memBlock != nullptr)
{
return memBlock;
}
AllocationVerboseTrace(recycler->GetRecyclerFlagsTable(), _u("TryAllocFromNewHeapBlock failed- triggering OOM handler"));
if (nothrow == false)
{
// Can't add a heap block, we are out of memory
// Since we're allowed to throw, throw right here
recycler->OutOfMemory();
}
return nullptr;
}
template <typename TBlockType>
TBlockType*
HeapBucketT<TBlockType>::GetUnusedHeapBlock()
{
// Add a new heap block
TBlockType * heapBlock = emptyBlockList;
if (heapBlock == nullptr)
{
// We couldn't find a reusable heap block
heapBlock = TBlockType::New(this);
#if defined(RECYCLER_SLOW_CHECK_ENABLED)
Assert(this->emptyHeapBlockCount == 0);
#endif
}
else
{
emptyBlockList = heapBlock->GetNextBlock();
#if defined(RECYCLER_SLOW_CHECK_ENABLED)
this->emptyHeapBlockCount--;
#endif
}
return heapBlock;
}
template <typename TBlockType>
TBlockType *
HeapBucketT<TBlockType>::CreateHeapBlock(Recycler * recycler)
{
FAULTINJECT_MEMORY_NOTHROW(_u("HeapBlock"), sizeof(TBlockType));
// Add a new heap block
TBlockType * heapBlock = GetUnusedHeapBlock();
if (heapBlock == nullptr)
{
return nullptr;
}
if (!heapBlock->ReassignPages(recycler))
{
FreeHeapBlock(heapBlock);
return nullptr;
}
// Add it to head of heap block list so we will keep track of the block
this->heapInfo->AppendNewHeapBlock(heapBlock, this);
#if defined(RECYCLER_SLOW_CHECK_ENABLED) || ENABLE_ALLOCATIONS_DURING_CONCURRENT_SWEEP
#if ENABLE_CONCURRENT_GC
::InterlockedIncrement(&this->newHeapBlockCount);
#else
this->heapBlockCount++;
#endif
#endif
return heapBlock;
}
template <typename TBlockType>
void
HeapBucketT<TBlockType>::FreeHeapBlock(TBlockType * heapBlock)
{
heapBlock->Reset();
heapBlock->SetNextBlock(emptyBlockList);
emptyBlockList = heapBlock;
#if defined(RECYCLER_SLOW_CHECK_ENABLED)
this->emptyHeapBlockCount++;
#endif
}
template <typename TBlockType>
void
HeapBucketT<TBlockType>::ResetMarks(ResetMarkFlags flags)
{
RECYCLER_SLOW_CHECK(this->VerifyHeapBlockCount((flags & ResetMarkFlags_Background) != 0));
#if !ENABLE_CONCURRENT_GC
Assert((flags & ResetMarkFlags_Background) == 0);
#endif
if ((flags & ResetMarkFlags_Background) == 0)
{
// The is equivalent to the ClearAllocators in Rescan
// But since we are not doing concurrent, we need to do it here.
ClearAllocators();
}
// Note, mark bits are now cleared in HeapBlockMap32::ResetMarks, so we don't need to clear them here.
if ((flags & ResetMarkFlags_ScanImplicitRoot) != 0)
{
HeapBlockList::ForEach(fullBlockList, [flags](TBlockType * heapBlock)
{
heapBlock->MarkImplicitRoots();
Assert(!heapBlock->HasFreeObject());
});
#if ENABLE_ALLOCATIONS_DURING_CONCURRENT_SWEEP && SUPPORT_WIN32_SLIST
if (CONFIG_FLAG_RELEASE(EnableConcurrentSweepAlloc) && !this->IsAnyFinalizableBucket())
{
HeapBlockList::ForEach(sweepableHeapBlockList, [flags](TBlockType * heapBlock)
{
heapBlock->MarkImplicitRoots();
});
}
#endif
HeapBlockList::ForEach(heapBlockList, [flags](TBlockType * heapBlock)
{
heapBlock->MarkImplicitRoots();
});
}
#if DBG
if ((flags & ResetMarkFlags_Background) == 0)
{
// When allocations are enabled for buckets during oncurrent sweep we don't keep track of the nextAllocableBlockHead as it directly
// comes out of the SLIST. As a result, the below validations can't be performed reliably on a heap block.
#if ENABLE_ALLOCATIONS_DURING_CONCURRENT_SWEEP
if (!CONFIG_FLAG_RELEASE(EnableConcurrentSweepAlloc) || this->IsAnyFinalizableBucket())
#endif
{
// Verify that if you are in the heapBlockList, before the nextAllocableBlockHead, we have fully allocated from
// the block already, except if we have cleared from the allocator, or it is still in the allocator
HeapBlockList::ForEach(heapBlockList, nextAllocableBlockHead, [](TBlockType * heapBlock)
{
// If the heap block is in the allocator, then the heap block may or may not have free object still
// So we can't assert. Otherwise, we have free object iff we were cleared from allocator
Assert(heapBlock->IsInAllocator() || heapBlock->HasFreeObject() == heapBlock->IsClearedFromAllocator());
});
// We should still have allocable free object after nextAllocableBlockHead
HeapBlockList::ForEach(nextAllocableBlockHead, [](TBlockType * heapBlock)
{
Assert(heapBlock->HasFreeObject());
});
}
}
#endif
}
template <typename TBlockType>
void
HeapBucketT<TBlockType>::ScanNewImplicitRoots(Recycler * recycler)
{
HeapBlockList::ForEach(fullBlockList, [recycler](TBlockType * heapBlock)
{
heapBlock->ScanNewImplicitRoots(recycler);
});
#if ENABLE_ALLOCATIONS_DURING_CONCURRENT_SWEEP && SUPPORT_WIN32_SLIST
if (CONFIG_FLAG_RELEASE(EnableConcurrentSweepAlloc) && !this->IsAnyFinalizableBucket())
{
HeapBlockList::ForEach(sweepableHeapBlockList, [recycler](TBlockType * heapBlock)
{
heapBlock->ScanNewImplicitRoots(recycler);
});
}
#endif
HeapBlockList::ForEach(heapBlockList, [recycler](TBlockType * heapBlock)
{
heapBlock->ScanNewImplicitRoots(recycler);
});
}
#if DBG
template <typename TBlockType>
void
HeapBucketT<TBlockType>::VerifyBlockConsistencyInList(TBlockType * heapBlock, RecyclerVerifyListConsistencyData& recyclerSweep)
{
bool* expectFull = nullptr;
bool* expectDispose = nullptr;
HeapBlock* nextAllocableBlockHead = nullptr;
if (TBlockType::HeapBlockAttributes::IsSmallBlock)
{
expectFull = &recyclerSweep.smallBlockVerifyListConsistencyData.expectFull;
expectDispose = &recyclerSweep.smallBlockVerifyListConsistencyData.expectDispose;
nextAllocableBlockHead = recyclerSweep.smallBlockVerifyListConsistencyData.nextAllocableBlockHead;
}
else if (TBlockType::HeapBlockAttributes::IsMediumBlock)
{
expectFull = &recyclerSweep.mediumBlockVerifyListConsistencyData.expectFull;
expectDispose = &recyclerSweep.mediumBlockVerifyListConsistencyData.expectDispose;
nextAllocableBlockHead = recyclerSweep.mediumBlockVerifyListConsistencyData.nextAllocableBlockHead;
}
else
{
Assert(false);
}
if (heapBlock == nextAllocableBlockHead)
{
(*expectFull) = false;
}
if (heapBlock->IsClearedFromAllocator())
{
#if ENABLE_ALLOCATIONS_DURING_CONCURRENT_SWEEP
// As the blocks are added to a SLIST and used from there during concurrent sweep, the expectFull assertion doesn't hold anymore.
// We could do some work to make this work again but there may be perf hit and it may be fragile.
if (!CONFIG_FLAG_RELEASE(EnableConcurrentSweepAlloc))
#endif
{
Assert(*expectFull && !*expectDispose);
Assert(heapBlock->HasFreeObject());
Assert(!heapBlock->HasAnyDisposeObjects());
}
}
else if (*expectDispose)
{
Assert(heapBlock->IsAnyFinalizableBlock() && heapBlock->template AsFinalizableBlock<typename TBlockType::HeapBlockAttributes>()->IsPendingDispose());
Assert(heapBlock->HasAnyDisposeObjects());
}
else
{
Assert(!heapBlock->HasAnyDisposeObjects());
// ExpectFull is a bit of a misnomer if the list in question is the heap block list. It's there to check
// of the heap block in question is before the nextAllocableBlockHead or not. This is to ensure that
// blocks before nextAllocableBlockHead that are not being bump allocated from must be considered "full".
// However, the exception is if this is the only heap block in this bucket, in which case nextAllocableBlockHead
// would be null
#if ENABLE_ALLOCATIONS_DURING_CONCURRENT_SWEEP
// As the blocks are added to the SLIST and used from there during concurrent sweep, the expectFull assertion doesn't hold anymore.
if (!CONFIG_FLAG_RELEASE(EnableConcurrentSweepAlloc))
#endif
{
Assert(*expectFull == (!heapBlock->HasFreeObject() || heapBlock->IsInAllocator()) || nextAllocableBlockHead == nullptr);
}
}
}
template <typename TBlockType>
void
HeapBucketT<TBlockType>::VerifyBlockConsistencyInList(TBlockType * heapBlock, RecyclerVerifyListConsistencyData const& recyclerSweep, SweepState state)
{
bool expectFull = false;
bool expectDispose = false;
if (TBlockType::HeapBlockAttributes::IsSmallBlock)
{
expectFull = recyclerSweep.smallBlockVerifyListConsistencyData.expectFull;
expectDispose = recyclerSweep.smallBlockVerifyListConsistencyData.expectDispose;
}
else if (TBlockType::HeapBlockAttributes::IsMediumBlock)
{
expectFull = recyclerSweep.mediumBlockVerifyListConsistencyData.expectFull;
expectDispose = recyclerSweep.mediumBlockVerifyListConsistencyData.expectDispose;
}
else
{
Assert(false);
}
if (heapBlock->IsClearedFromAllocator())
{
// this function is called during sweep and we are recreating the heap block list
// which would make all the block to be in it's rightful place
heapBlock->SetIsClearedFromAllocator(false);
Assert(SweepStateFull != state);
}
else
{
// You can still be full only if you are full before.
Assert(expectFull || SweepStateFull != state);
}
// If you were pending dispose before, you can only be pending dispose after
Assert(!expectDispose || SweepStatePendingDispose == state);
}
#endif // DBG
#if ENABLE_PARTIAL_GC
template <typename TBlockType>
bool
HeapBucketT<TBlockType>::DoQueuePendingSweep(Recycler * recycler)
{
return IsNormalBucket && recycler->inPartialCollectMode;
}
template <typename TBlockType>
bool
HeapBucketT<TBlockType>::DoPartialReuseSweep(Recycler * recycler)
{
// With leaf, we don't need to do a partial sweep
// WriteBarrier-TODO: We shouldn't need to do this for write barrier heap buckets either
return !IsLeafBucket && recycler->inPartialCollectMode;
}
#endif
template <typename TBlockType>
void
HeapBucketT<TBlockType>::SweepHeapBlockList(RecyclerSweep& recyclerSweep, TBlockType * heapBlockList, bool allocable)
{
#if DBG
if (TBlockType::HeapBlockAttributes::IsSmallBlock)
{
Assert(recyclerSweep.smallBlockVerifyListConsistencyData.hasSetupVerifyListConsistencyData);
recyclerSweep.smallBlockVerifyListConsistencyData.hasSetupVerifyListConsistencyData = false;
}
else if (TBlockType::HeapBlockAttributes::IsMediumBlock)
{
Assert(recyclerSweep.mediumBlockVerifyListConsistencyData.hasSetupVerifyListConsistencyData);
recyclerSweep.mediumBlockVerifyListConsistencyData.hasSetupVerifyListConsistencyData = false;
}
else
{
Assert(false);
}
#endif
Recycler * recycler = recyclerSweep.GetRecycler();
// Whether we run in thread or background thread, we want to queue up pending sweep
// only if we are doing partial GC so we can calculate the heuristics before
// determinate we want to fully sweep the block or partially sweep the block
#if ENABLE_PARTIAL_GC
// CONCURRENT-TODO: Add a mode where we can do in thread sweep, and concurrent partial sweep?
bool const queuePendingSweep = this->DoQueuePendingSweep(recycler);
#else
bool const queuePendingSweep = false;
#endif
#if ENABLE_ALLOCATIONS_DURING_CONCURRENT_SWEEP
Assert(this->IsAllocationStopped() || this->AllocationsStartedDuringConcurrentSweep());
#else
Assert(this->IsAllocationStopped());
#endif
HeapBlockList::ForEachEditing(heapBlockList, [=, &recyclerSweep](TBlockType * heapBlock)
{
// The whole list need to be consistent
DebugOnly(VerifyBlockConsistencyInList(heapBlock, recyclerSweep));
#ifdef RECYCLER_TRACE
recyclerSweep.GetRecycler()->PrintBlockStatus(this, heapBlock, _u("[**1**] starting Sweep Pass1."));
#endif
SweepState state = heapBlock->Sweep(recyclerSweep, queuePendingSweep, allocable);
DebugOnly(VerifyBlockConsistencyInList(heapBlock, recyclerSweep, state));
switch (state)
{
#if ENABLE_CONCURRENT_GC
case SweepStatePendingSweep:
{
Assert(IsNormalBucket);
// blocks that have swept object. Queue up the block for concurrent sweep.
Assert(queuePendingSweep);
TBlockType *& pendingSweepList = recyclerSweep.GetPendingSweepBlockList(this);
DebugOnly(this->AssertCheckHeapBlockNotInAnyList(heapBlock));
AssertMsg(!HeapBlockList::Contains(heapBlock, pendingSweepList), "The heap block already exists in the pendingSweepList.");
heapBlock->SetNextBlock(pendingSweepList);
pendingSweepList = heapBlock;
#ifdef RECYCLER_TRACE
recyclerSweep.GetRecycler()->PrintBlockStatus(this, heapBlock, _u("[**2**] finished Sweep Pass1, heapblock added to pendingSweepList."));
#endif
#if ENABLE_PARTIAL_GC
recyclerSweep.GetManager()->NotifyAllocableObjects(heapBlock);
#endif
break;
}
#endif
case SweepStatePendingDispose:
{
Assert(!recyclerSweep.IsBackground());
#ifdef RECYCLER_WRITE_BARRIER
Assert(IsFinalizableBucket || IsFinalizableWriteBarrierBucket);
#else
Assert(IsFinalizableBucket);
#endif
#if ENABLE_ALLOCATIONS_DURING_CONCURRENT_SWEEP
if (CONFIG_FLAG_RELEASE(EnableConcurrentSweepAlloc))
{
AssertMsg(!heapBlock->isPendingConcurrentSweepPrep, "Finalizable blocks don't support allocations during concurrent sweep.");
}
#endif
DebugOnly(heapBlock->template AsFinalizableBlock<typename TBlockType::HeapBlockAttributes>()->SetIsPendingDispose());
// These are the blocks that have swept finalizable object
// We already transferred the non finalizable swept objects when we are not doing
// concurrent collection, so we only need to queue up the blocks that have
// finalizable objects, so that we can go through and call the dispose, and then
// transfer the finalizable object back to the free list.
SmallFinalizableHeapBucketT<typename TBlockType::HeapBlockAttributes> * finalizableHeapBucket = (SmallFinalizableHeapBucketT<typename TBlockType::HeapBlockAttributes>*)this;
DebugOnly(this->AssertCheckHeapBlockNotInAnyList(heapBlock));
//AssertMsg(!HeapBlockList::Contains(heapBlock, finalizableHeapBucket->pendingDisposeList), "The heap block already exists in the pendingDisposeList.");
heapBlock->template AsFinalizableBlock<typename TBlockType::HeapBlockAttributes>()->SetNextBlock(finalizableHeapBucket->pendingDisposeList);
finalizableHeapBucket->pendingDisposeList = heapBlock->template AsFinalizableBlock<typename TBlockType::HeapBlockAttributes>();
Assert(!this->heapInfo->hasPendingTransferDisposedObjects);
recycler->hasDisposableObject = true;
#ifdef RECYCLER_TRACE
recyclerSweep.GetRecycler()->PrintBlockStatus(this, heapBlock, _u("[**3**] finished Sweep Pass1, heapblock added to pendingDisposeList."));
#endif
break;
}
case SweepStateSwept:
{
Assert(this->nextAllocableBlockHead == nullptr);
Assert(heapBlock->HasFreeObject());
DebugOnly(this->AssertCheckHeapBlockNotInAnyList(heapBlock));
#if ENABLE_ALLOCATIONS_DURING_CONCURRENT_SWEEP && SUPPORT_WIN32_SLIST
if (this->AllocationsStartedDuringConcurrentSweep())
{
Assert(!this->IsAnyFinalizableBucket());
Assert(!heapBlock->isPendingConcurrentSweepPrep);
bool blockAddedToSList = HeapBucketT<TBlockType>::PushHeapBlockToSList(this->allocableHeapBlockListHead, heapBlock);
// If we encountered OOM while pushing the heapBlock to the SLIST we must add it to the heapBlockList so we don't lose track of it.
if (!blockAddedToSList)
{
//TODO: akatti: We should handle this gracefully and try to recover from this state.
AssertOrFailFastMsg(false, "OOM while adding a heap block to the SLIST during concurrent sweep.");
}
#ifdef RECYCLER_TRACE
else
{
this->GetRecycler()->PrintBlockStatus(this, heapBlock, _u("[**4**] swept and added to SLIST allocableHeapBlockListHead during Pass1."));
}
#endif
}
else
#endif
{
#if ENABLE_ALLOCATIONS_DURING_CONCURRENT_SWEEP
AssertMsg(!this->AllowAllocationsDuringConcurrentSweep(), "Why are allocations not started during concurrent sweep?");
#endif
heapBlock->SetNextBlock(this->heapBlockList);
this->heapBlockList = heapBlock;
}
#ifdef RECYCLER_TRACE
recyclerSweep.GetRecycler()->PrintBlockStatus(this, heapBlock, _u("[**6**] finished Sweep Pass1, heapblock added to heapBlockList."));
#endif
#if ENABLE_PARTIAL_GC
recyclerSweep.GetManager()->NotifyAllocableObjects(heapBlock);
#endif
break;
}
case SweepStateFull:
{
Assert(!heapBlock->HasFreeObject());
DebugOnly(this->AssertCheckHeapBlockNotInAnyList(heapBlock));
heapBlock->SetNextBlock(this->fullBlockList);
this->fullBlockList = heapBlock;
#ifdef RECYCLER_TRACE
recyclerSweep.GetRecycler()->PrintBlockStatus(this, heapBlock, _u("[**7**] finished Sweep Pass1, heapblock FULL added to fullBlockList."));
#endif
break;
}
case SweepStateEmpty:
{
// the block is empty, just free them
#ifdef RECYCLER_MEMORY_VERIFY
// Let's verify it before we free it
if (recycler->VerifyEnabled())
{
heapBlock->Verify();
}
#endif
RECYCLER_STATS_INC(recycler, numEmptySmallBlocks[heapBlock->GetHeapBlockType()]);
#if ENABLE_CONCURRENT_GC
// CONCURRENT-TODO: Finalizable block never have background == true and always be processed
// in thread, so it will not queue up the pages even if we are doing concurrent GC
DebugOnly(this->AssertCheckHeapBlockNotInAnyList(heapBlock));
#if ENABLE_ALLOCATIONS_DURING_CONCURRENT_SWEEP
if (CONFIG_FLAG_RELEASE(EnableConcurrentSweepAlloc))
{
AssertMsg(heapBlock->objectsAllocatedDuringConcurrentSweepCount == 0, "We allocated to this block during concurrent sweep; it's not EMPTY anymore, it should NOT be freed or queued as EMPTY.");
}
#endif
if (recyclerSweep.IsBackground())
{
#ifdef RECYCLER_WRITE_BARRIER
Assert(!(IsFinalizableBucket || IsFinalizableWriteBarrierBucket));
#else
Assert(!IsFinalizableBucket);
#endif
// CONCURRENT-TODO: We will zero heap block even if the number free page pool exceed
// the maximum and will get decommitted anyway
recyclerSweep.template QueueEmptyHeapBlock<TBlockType>(this, heapBlock);
RECYCLER_STATS_INC(recycler, numZeroedOutSmallBlocks);
#ifdef RECYCLER_TRACE
recyclerSweep.GetRecycler()->PrintBlockStatus(this, heapBlock, _u("[**8**] finished Sweep Pass1, heapblock EMPTY added to pendingEmptyBlockList."));
#endif
}
else
#endif
{
// Just free the page in thread (and zero the page)
heapBlock->ReleasePagesSweep(recycler);
FreeHeapBlock(heapBlock);
#if defined(RECYCLER_SLOW_CHECK_ENABLED) || ENABLE_ALLOCATIONS_DURING_CONCURRENT_SWEEP
this->heapBlockCount--;
#endif
#ifdef RECYCLER_TRACE
recyclerSweep.GetRecycler()->PrintBlockStatus(this, heapBlock, _u("[**9**] finished Sweep Pass1, heapblock EMPTY, was FREED in-thread."));
#endif
}
break;
}
}
});
}
template <typename TBlockType>
void
HeapBucketT<TBlockType>::SweepBucket(RecyclerSweep& recyclerSweep)
{
DebugOnly(TBlockType * savedNextAllocableBlockHead);
RECYCLER_SLOW_CHECK(this->VerifyHeapBlockCount(recyclerSweep.IsBackground()));
#if ENABLE_CONCURRENT_GC
if (recyclerSweep.GetManager()->HasSetupBackgroundSweep())
{
// SetupBackgroundSweep set nextAllocableBlockHead to null already
Assert(IsAllocationStopped());
DebugOnly(savedNextAllocableBlockHead = recyclerSweep.GetSavedNextAllocableBlockHead(this));
}
else
#endif
{
Assert(AllocatorsAreEmpty());
DebugOnly(savedNextAllocableBlockHead = this->nextAllocableBlockHead);
this->StopAllocationBeforeSweep();
}
// We just started sweeping. These pending lists should be empty
#if ENABLE_CONCURRENT_GC
Assert(recyclerSweep.GetPendingSweepBlockList(this) == nullptr);
#else
Assert(!recyclerSweep.IsBackground());
#endif
#if ENABLE_ALLOCATIONS_DURING_CONCURRENT_SWEEP && SUPPORT_WIN32_SLIST
if (CONFIG_FLAG_RELEASE(EnableConcurrentSweepAlloc) && this->sweepableHeapBlockList != nullptr)
{
Assert(!this->IsAnyFinalizableBucket());
#if DBG || defined(RECYCLER_SLOW_CHECK_ENABLED)
// This lock is needed only in the debug mode while we verify block counts. Not needed otherwise, as this list is never accessed concurrently.
// Items are added to it by the allocator when allocations are allowed during concurrent sweep. The list is drained during the next sweep while
// allocation are stopped.
debugSweepableHeapBlockListLock.Enter();
#endif
// Return the blocks we may have allocated from during the previous concurrent sweep back to the fullBlockList.
// We need to rebuild the free bit vectors for these blocks.
HeapBlockList::ForEachEditing(this->sweepableHeapBlockList, [this](TBlockType * heapBlock)
{
heapBlock->BuildFreeBitVector();
AssertMsg(!HeapBlockList::Contains(heapBlock, heapBlockList), "The heap block already exists in the heapBlockList.");
AssertMsg(!HeapBlockList::Contains(heapBlock, fullBlockList), "The heap block already exists in the fullBlockList.");
AssertMsg(!HeapBlockList::Contains(heapBlock, emptyBlockList), "The heap block already exists in the emptyBlockList.");
heapBlock->SetNextBlock(this->fullBlockList);
this->fullBlockList = heapBlock;
});
this->sweepableHeapBlockList = nullptr;
#if DBG || defined(RECYCLER_SLOW_CHECK_ENABLED)
debugSweepableHeapBlockListLock.Leave();
#endif
}
#endif
#if DBG
if (TBlockType::HeapBlockAttributes::IsSmallBlock)
{
recyclerSweep.SetupVerifyListConsistencyDataForSmallBlock((SmallHeapBlock*) savedNextAllocableBlockHead, true, false);
}
else if (TBlockType::HeapBlockAttributes::IsMediumBlock)
{
recyclerSweep.SetupVerifyListConsistencyDataForMediumBlock((MediumHeapBlock*) savedNextAllocableBlockHead, true, false);
}
else
{
Assert(false);
}
#endif
// Move the list locally. We will relink them during sweep
TBlockType * currentFullBlockList = fullBlockList;
TBlockType * currentHeapBlockList = heapBlockList;
this->heapBlockList = nullptr;
this->fullBlockList = nullptr;
#if ENABLE_ALLOCATIONS_DURING_CONCURRENT_SWEEP
// In order to allow allocations during sweep (Pass-1) we will set aside blocks after nextAllocableBlockHead (excluding) and allow
// allocations to these blocks as we know that these blocks are not full yet. These will need to be swept later though before starting Pass-2
// of the sweep.
this->PrepareForAllocationsDuringConcurrentSweep(currentHeapBlockList);
#endif
this->SweepHeapBlockList(recyclerSweep, currentHeapBlockList, true);
#if DBG
if (TBlockType::HeapBlockAttributes::IsSmallBlock)
{
recyclerSweep.SetupVerifyListConsistencyDataForSmallBlock(nullptr, true, false);
}
else if (TBlockType::HeapBlockAttributes::IsMediumBlock)
{
recyclerSweep.SetupVerifyListConsistencyDataForMediumBlock(nullptr, true, false);
}
else
{
Assert(false);
}
#endif
this->SweepHeapBlockList(recyclerSweep, currentFullBlockList, false);
// We shouldn't have allocate from any block yet
Assert(this->nextAllocableBlockHead == nullptr);
}
template <typename TBlockType>
bool
HeapBucketT<TBlockType>::AllowAllocationsDuringConcurrentSweep()
{
#if ENABLE_ALLOCATIONS_DURING_CONCURRENT_SWEEP
Recycler * recycler = this->GetRecycler();
if (!CONFIG_FLAG_RELEASE(EnableConcurrentSweepAlloc) || !recycler->AllowAllocationsDuringConcurrentSweep() || !this->concurrentSweepAllocationsThresholdExceeded)
{
return false;
}
#if ENABLE_PARTIAL_GC
bool isPartialGC = (recycler->recyclerSweepManager != nullptr) && recycler->recyclerSweepManager->InPartialCollect();
#else
bool isPartialGC = false;
#endif
// Allocations are allowed during concurrent sweep for small non-finalizable buckets while not doing a Partial GC.
return (recycler->IsConcurrentSweepSetupState() || recycler->InConcurrentSweep()) && !this->IsAnyFinalizableBucket() && !isPartialGC;
#else
return false;
#endif
}
template <typename TBlockType>
void
HeapBucketT<TBlockType>::StopAllocationBeforeSweep()
{
#if ENABLE_ALLOCATIONS_DURING_CONCURRENT_SWEEP
this->allocationsStartedDuringConcurrentSweep = false;
#if SUPPORT_WIN32_SLIST
this->lastKnownNextAllocableBlockHead = this->nextAllocableBlockHead;
#endif
#endif
Assert(!this->IsAllocationStopped());
this->isAllocationStopped = true;
this->nextAllocableBlockHead = nullptr;
}
template <typename TBlockType>
void
HeapBucketT<TBlockType>::StartAllocationAfterSweep()
{
Assert(this->IsAllocationStopped());
this->isAllocationStopped = false;
this->nextAllocableBlockHead = this->heapBlockList;
}
#if ENABLE_ALLOCATIONS_DURING_CONCURRENT_SWEEP
template <typename TBlockType>
void
HeapBucketT<TBlockType>::StartAllocationDuringConcurrentSweep()
{
Recycler * recycler = this->GetRecycler();
Assert(!recycler->recyclerSweepManager->InPartialCollect());
Assert(!this->IsAnyFinalizableBucket());
Assert(this->IsAllocationStopped());
this->isAllocationStopped = false;
Assert(!this->allocationsStartedDuringConcurrentSweep);
this->allocationsStartedDuringConcurrentSweep = true;
#if SUPPORT_WIN32_SLIST
// When allocations are allowed during concurrent sweep we set nextAllocableBlockHead to NULL as the allocator will pick heap blocks from the
// interlocked SLIST. During that time, the heap block at the top of the SLIST is always the nextAllocableBlockHead.
this->nextAllocableBlockHead = nullptr;
this->lastKnownNextAllocableBlockHead = nullptr;
#endif
}
template <typename TBlockType>
void
HeapBucketT<TBlockType>::ResumeNormalAllocationAfterConcurrentSweep(TBlockType * newNextAllocableBlockHead)
{
this->allocationsStartedDuringConcurrentSweep = false;
this->isAllocationStopped = false;
// If the newNextAllocableBlockHead is NULL at this point that means we have exhausted usable blocks and will have to allocate a new block the next time.
this->nextAllocableBlockHead = newNextAllocableBlockHead;
}
/*//////////////////////////////////////////////////////////////////////////////////////////////////////
If allocations are to be allowed to existing heap blocks during concurrent sweep, we set aside a few
heap blocks from the heapBlockList prior to beginning sweep. However, we eed to then go back and make
sure these blocks also swept before this sweep finishes. In order to do this we clearly define concurrent
sweep having 2 passes now. These passes existed before but were not distiguished as they would always start
and finish in one go on the background thread. However, whenever allocations are allowed during concurrent
sweep; the concurrent sweep will start Pass1 on the background thread, wait to finish Pass1 of the blocks
we set aside to allocate from on the main thread and then go back to finish Pass2 for all heap blocks on
the background thread. Note that, due to this need to finish Pass1 on the foreground thread the overall
background sweep will now appear to take longer whenever we chose to do such a two-pass sweep.
The sequence of things we do to allow allocations during concurrent sweep is described below:
1. At the beginning of concurrrent sweep we decide if we will benefit from allowing allocations during concurrent
sweep for any of the buckets. If there is at-least one bucket for which we think we will benefit we will turn on
allocations during concurrent sweep. Once turned on we will attempt to enable allocations during concurrent sweep
for all supported buckets (i.e. small/medium, normal/leaf, non-finalizable buckets.write barrrier bickets are supported
as well.).
2. If allocations are turned on during concurrent sweep, we will see if there are any allocable blocks in the
heapBlockList after the nextAllocableBlockHead. If we find any such blocks, we move them to a SLIST that the
allocator can pick these blocks from during sweep.
3. CollectionStateConcurrentSweepPass1: We will finish Pass1 of the sweep for all the remaining blocks (other than the
ones we put in the SLIST in step 2 above) This will generally happen on the background thread unless we are forcing
in-thread sweep. This state is now specifically identified as CollectionStateConcurrentSweepPass1.
4. CollectionStateConcurrentSweepPass1Wait: At this point we need to wait for all the blocks that we put in the SLIST
to also finish the Pass1 of the sweep. This needs to happen on the foreground thread so we prevent the allocator from
picking up the blocks from SLIST while we do this. This state is now identified as CollectionStateConcurrentSweepPass1Wait.
5. CollectionStateConcurrentSweepPass2: At this point we will do the actual sweeping of all the blocks that are not yet swept,
for eaxample, any blocks that were put onto the pendingSweepList. As these blocks get swept we keep adding them to the
SLIST again to allow allocators to allocate from them as soon as they are swept.
6. Before the Pass2 can finish we can call this concurrent sweep done we need to move all the blocks off of the SLIST so
that normal allocations can begin after the sweep. This is the last step of the concurrent sweep.
//////////////////////////////////////////////////////////////////////////////////////////////////////*/
template<typename TBlockType>
void
HeapBucketT<TBlockType>::PrepareForAllocationsDuringConcurrentSweep(TBlockType * &currentHeapBlockList)
{
#if SUPPORT_WIN32_SLIST
if (this->AllowAllocationsDuringConcurrentSweep())
{
this->EnsureAllocableHeapBlockList();
Assert(!this->IsAnyFinalizableBucket());
Assert(HeapBucketT<TBlockType>::QueryDepthInterlockedSList(this->allocableHeapBlockListHead) == 0);
Assert(HeapBlockList::Count(this->sweepableHeapBlockList) == 0);
TBlockType* startingNextAllocableBlockHead = this->lastKnownNextAllocableBlockHead;
bool allocationsStarted = false;
if (startingNextAllocableBlockHead != nullptr)
{
// To avoid a race condition between the allocator attempting to allocate from the lastKnownNextAllocableBlockHead and this code
// where we are adding it to the SLIST we skip the lastKnownNextAllocableBlockHead and pick up the next block to start with.
// Allocations should have stopped by then; so allocator shouldn't pick up the lastKnownNextAllocableBlockHead->Next block.
TBlockType* savedNextAllocableBlockHead = startingNextAllocableBlockHead->GetNextBlock();
startingNextAllocableBlockHead->SetNextBlock(nullptr);
startingNextAllocableBlockHead = savedNextAllocableBlockHead;
if (startingNextAllocableBlockHead != nullptr)
{
// The allocable blocks, if any are available, will now be added to the allocable blocks SLIST at this time; start allocations now.
this->StartAllocationDuringConcurrentSweep();
allocationsStarted = true;
HeapBlockList::ForEachEditing(startingNextAllocableBlockHead, [this, &allocationsStarted](TBlockType * heapBlock)
{
// This heap block is NOT ready to be swept concurrently as it hasn't yet been through sweep prep (i.e. Pass1 of sweep).
heapBlock->isPendingConcurrentSweepPrep = true;
DebugOnly(this->AssertCheckHeapBlockNotInAnyList(heapBlock));
bool blockAddedToSList = HeapBucketT<TBlockType>::PushHeapBlockToSList(this->allocableHeapBlockListHead, heapBlock);
// If we encountered OOM while pushing the heapBlock to the SLIST we must add it to the heapBlockList so we don't lose track of it.
if (!blockAddedToSList)
{
//TODO: akatti: We should handle this gracefully and try to recover from this state.
AssertOrFailFastMsg(false, "OOM while adding a heap block to the SLIST during concurrent sweep.");
}
else
{
#ifdef RECYCLER_TRACE
this->GetRecycler()->PrintBlockStatus(this, heapBlock, _u("[**5**] added to SLIST before Pass1."));
#endif
}
});
#ifdef RECYCLER_TRACE
if (this->GetRecycler()->GetRecyclerFlagsTable().Trace.IsEnabled(Js::ConcurrentSweepPhase) && CONFIG_FLAG_RELEASE(Verbose))
{
size_t currentHeapBlockCount = QueryDepthInterlockedSList(allocableHeapBlockListHead);
CollectionState collectionState = this->GetRecycler()->collectionState;
Output::Print(_u("[GC #%d] [HeapBucket 0x%p] Starting allocations during concurrent sweep with %d blocks. [CollectionState: %d] \n"), this->GetRecycler()->collectionCount, this, currentHeapBlockCount, collectionState);
Output::Print(_u("[GC #%d] [HeapBucket 0x%p] The heapBlockList has %d blocks. Total heapBlockCount is %d.\n\n"), this->GetRecycler()->collectionCount, this, HeapBlockList::Count(this->heapBlockList), this->heapBlockCount);
}
#endif
}
}
if (!allocationsStarted)
{
// If we didn't start allocations yet, start them now in anticipation of blocks becoming available later as blocks complete sweep.
this->StartAllocationDuringConcurrentSweep();
allocationsStarted = true;
}
Assert(!this->IsAllocationStopped());
}
#endif
}
#endif
template <typename TBlockType>
bool
HeapBucketT<TBlockType>::IsAllocationStopped() const
{
if (this->isAllocationStopped)
{
Assert(this->nextAllocableBlockHead == nullptr);
return true;
}
return false;
}
template <typename TBlockType>
uint
HeapBucketT<TBlockType>::Rescan(Recycler * recycler, RescanFlags flags)
{
#if ENABLE_CONCURRENT_GC
RECYCLER_SLOW_CHECK(this->VerifyHeapBlockCount(!!recycler->IsConcurrentMarkState()));
#else
RECYCLER_SLOW_CHECK(this->VerifyHeapBlockCount(false /* background */));
#endif
#if ENABLE_CONCURRENT_GC
// If we do the final rescan concurrently, the main thread will prepare for sweep concurrently
// If we do rescan in thread, we will need to prepare sweep here.
// However, if we are in the rescan for OOM, we have already done it, so no need to do it again
if (!recycler->IsConcurrentMarkState() && !recycler->inEndMarkOnLowMemory)
{
this->PrepareSweep();
}
#endif
// By default heap bucket doesn't rescan anything
return 0;
}
#if ENABLE_CONCURRENT_GC
template <typename TBlockType>
void
HeapBucketT<TBlockType>::MergeNewHeapBlock(TBlockType * heapBlock)
{
Assert(heapBlock->GetObjectSize() == this->sizeCat);
heapBlock->SetNextBlock(this->heapBlockList);
this->heapBlockList = heapBlock;
#if defined(RECYCLER_SLOW_CHECK_ENABLED) || ENABLE_ALLOCATIONS_DURING_CONCURRENT_SWEEP
::InterlockedDecrement(&this->newHeapBlockCount);
this->heapBlockCount++;
#endif
}
template <typename TBlockType>
void
HeapBucketT<TBlockType>::SetupBackgroundSweep(RecyclerSweep& recyclerSweep)
{
// Don't allocate from existing block temporary when concurrent sweeping
// Currently Rescan clear allocators, if we remove the uncollectedAllocBytes there, we can
// avoid it there and do it here.
Assert(this->AllocatorsAreEmpty());
DebugOnly(recyclerSweep.SaveNextAllocableBlockHead(this));
Assert(recyclerSweep.GetPendingSweepBlockList(this) == nullptr);
this->StopAllocationBeforeSweep();
}
#endif
#if ENABLE_ALLOCATIONS_DURING_CONCURRENT_SWEEP
template <typename TBlockType>
void
HeapBucketT<TBlockType>::FinishConcurrentSweepPass1(RecyclerSweep& recyclerSweep)
{
if (this->concurrentSweepAllocationsThresholdExceeded)
{
AssertMsg(this->AllowAllocationsDuringConcurrentSweep(), "Why are we in two pass concurrent sweep?");
Assert(!this->IsAnyFinalizableBucket());
// Rebuild the free bit vectors for the blocks we allocated from during concurrent sweep.
TBlockType * currentPendingSweepPrepHeapBlockList = nullptr;
TBlockType * currentSweepableHeapBlockList = this->sweepableHeapBlockList;
this->sweepableHeapBlockList = nullptr;
HeapBlockList::ForEachEditing(currentSweepableHeapBlockList, [this, &currentPendingSweepPrepHeapBlockList](TBlockType * heapBlock)
{
if (heapBlock->isPendingConcurrentSweepPrep)
{
ushort previousFreeCount = heapBlock->freeCount;
heapBlock->BuildFreeBitVector();
#if ENABLE_PARTIAL_GC
heapBlock->oldFreeCount = heapBlock->lastFreeCount = heapBlock->freeCount;
#else
heapBlock->lastFreeCount = heapBlock->freeCount;
#endif
ushort newAllocatedObjects = previousFreeCount - heapBlock->freeCount;
AssertMsg(newAllocatedObjects == heapBlock->objectsMarkedDuringSweep, "The counts of objects allocated during sweep should match the objects marked during sweep.");
#if DBG || defined(RECYCLER_SLOW_CHECK_ENABLED)
heapBlock->objectsAllocatedDuringConcurrentSweepCount = newAllocatedObjects;
#endif
ushort currentMarkCount = (ushort)heapBlock->GetMarkCountForSweep();
heapBlock->markCount = currentMarkCount;
#if DBG
heapBlock->GetRecycler()->heapBlockMap.SetPageMarkCount(heapBlock->GetAddress(), currentMarkCount);
#endif
#ifdef RECYCLER_TRACE
heapBlock->GetRecycler()->PrintBlockStatus(this, heapBlock, _u("[**13**] ending sweep Pass1, rebuilt free bit vector and set page mark count to match."));
#endif
heapBlock->SetNextBlock(currentPendingSweepPrepHeapBlockList);
currentPendingSweepPrepHeapBlockList = heapBlock;
}
else
{
heapBlock->SetNextBlock(this->sweepableHeapBlockList);
this->sweepableHeapBlockList = heapBlock;
}
});
#if DBG || defined(RECYCLER_SLOW_CHECK_ENABLED)
// This lock is needed only in the debug mode while we verify block counts. Not needed otherwise, as this list is never accessed concurrently.
// Items are added to it by the allocator when allocations are allowed during concurrent sweep. The list is drained during the next sweep while
// allocation are stopped.
debugSweepableHeapBlockListLock.Enter();
#endif
// Pull the blocks from the allocable SLIST that we didn't use. We need to finish the Pass-1 sweep of these blocks too.
TBlockType * heapBlock = PopHeapBlockFromSList(this->allocableHeapBlockListHead);
while (heapBlock != nullptr)
{
DebugOnly(this->AssertCheckHeapBlockNotInAnyList(heapBlock));
if (heapBlock->isPendingConcurrentSweepPrep)
{
#ifdef RECYCLER_TRACE
heapBlock->GetRecycler()->PrintBlockStatus(this, heapBlock, _u("[**19**] ending sweep Pass1, removed from SLIST."));
#endif
heapBlock->SetNextBlock(currentPendingSweepPrepHeapBlockList);
currentPendingSweepPrepHeapBlockList = heapBlock;
}
else
{
#ifdef RECYCLER_TRACE
heapBlock->GetRecycler()->PrintBlockStatus(this, heapBlock, _u("[**23**] ending sweep Pass1, removed from SLIST and added to sweepableHeapBlockList."));
#endif
// Already swept, put it back to the sweepableHeapBlockList list; so it can be processed later.
heapBlock->SetNextBlock(this->sweepableHeapBlockList);
this->sweepableHeapBlockList = heapBlock;
}
heapBlock = PopHeapBlockFromSList(this->allocableHeapBlockListHead);
}
Assert(QueryDepthInterlockedSList(this->allocableHeapBlockListHead) == 0);
#if DBG || defined(RECYCLER_SLOW_CHECK_ENABLED)
debugSweepableHeapBlockListLock.Leave();
#endif
#if DBG
if (TBlockType::HeapBlockAttributes::IsSmallBlock)
{
recyclerSweep.SetupVerifyListConsistencyDataForSmallBlock(nullptr, true, false);
}
else if (TBlockType::HeapBlockAttributes::IsMediumBlock)
{
recyclerSweep.SetupVerifyListConsistencyDataForMediumBlock(nullptr, true, false);
}
else
{
Assert(false);
}
#endif
// Start allocations now as we may start adding blocks to the SLIST during Pass1 sweep below.
this->StartAllocationDuringConcurrentSweep();
this->SweepHeapBlockList(recyclerSweep, currentPendingSweepPrepHeapBlockList, true /*allocable*/);
}
}
template <typename TBlockType>
void
HeapBucketT<TBlockType>::EnsureAllocableHeapBlockList()
{
#if ENABLE_ALLOCATIONS_DURING_CONCURRENT_SWEEP && SUPPORT_WIN32_SLIST
if (CONFIG_FLAG_RELEASE(EnableConcurrentSweepAlloc))
{
if (allocableHeapBlockListHead == nullptr)
{
allocableHeapBlockListHead = ((PSLIST_HEADER)_aligned_malloc(sizeof(SLIST_HEADER), MEMORY_ALLOCATION_ALIGNMENT));
if (allocableHeapBlockListHead == nullptr)
{
this->heapInfo->recycler->OutOfMemory();
}
else
{
::InitializeSListHead(allocableHeapBlockListHead);
}
}
}
#endif
}
template <typename TBlockType>
void
HeapBucketT<TBlockType>::FinishSweepPrep(RecyclerSweep& recyclerSweep)
{
if (this->AllocationsStartedDuringConcurrentSweep())
{
AssertMsg(this->AllowAllocationsDuringConcurrentSweep(), "Why are allocations started during concurrent sweep, if not allowed?");
Assert(!this->IsAnyFinalizableBucket());
this->StopAllocationBeforeSweep();
this->ClearAllocators();
}
}
template <typename TBlockType>
void
HeapBucketT<TBlockType>::FinishConcurrentSweep()
{
if (this->AllocationsStartedDuringConcurrentSweep())
{
#if SUPPORT_WIN32_SLIST
Assert(!this->IsAnyFinalizableBucket());
Assert(this->allocableHeapBlockListHead != nullptr);
#ifdef RECYCLER_TRACE
if (this->GetRecycler()->GetRecyclerFlagsTable().Trace.IsEnabled(Js::ConcurrentSweepPhase) && CONFIG_FLAG_RELEASE(Verbose))
{
CollectionState collectionState = this->GetRecycler()->collectionState;
Output::Print(_u("[GC #%d] [HeapBucket 0x%p] starting FinishConcurrentSweep [CollectionState: %d] \n"), this->GetRecycler()->collectionCount, this, collectionState);
}
#endif
TBlockType * newNextAllocableBlockHead = nullptr;
// Put the blocks from the allocable SLIST into the heapBlockList.
TBlockType * heapBlock = PopHeapBlockFromSList(this->allocableHeapBlockListHead);
while (heapBlock != nullptr)
{
DebugOnly(this->AssertCheckHeapBlockNotInAnyList(heapBlock));
AssertMsg(!heapBlock->isPendingConcurrentSweepPrep, "The blocks in the SLIST at this time should NOT have sweep prep i.e. sweep-Pass1 pending.");
newNextAllocableBlockHead = heapBlock;
heapBlock->SetNextBlock(this->heapBlockList);
this->heapBlockList = heapBlock;
#ifdef RECYCLER_TRACE
this->GetRecycler()->PrintBlockStatus(this, heapBlock, _u("[**40**] finished FinishConcurrentSweep, heapblock removed from SLIST and added to heapBlockList."));
#endif
heapBlock = PopHeapBlockFromSList(this->allocableHeapBlockListHead);
}
Assert(QueryDepthInterlockedSList(this->allocableHeapBlockListHead) == 0);
this->ResumeNormalAllocationAfterConcurrentSweep(newNextAllocableBlockHead);
#endif
Assert(!this->IsAllocationStopped());
}
}
#endif
template <typename TBlockType>
void
HeapBucketT<TBlockType>::AppendAllocableHeapBlockList(TBlockType * list)
{
#ifdef RECYCLER_TRACE
if (this->GetRecycler()->GetRecyclerFlagsTable().Trace.IsEnabled(Js::ConcurrentSweepPhase) && CONFIG_FLAG_RELEASE(Verbose))
{
CollectionState collectionState = this->GetRecycler()->collectionState;
Output::Print(_u("[GC #%d] [HeapBucket 0x%p] in AppendAllocableHeapBlockList [CollectionState: %d] \n"), this->GetRecycler()->collectionCount, this, collectionState);
}
#endif
// Add the list to the end of the current list
TBlockType * currentHeapBlockList = this->heapBlockList;
if (currentHeapBlockList == nullptr)
{
// There weren't any heap block list before, just move the list over and start allocate from it
this->heapBlockList = list;
this->nextAllocableBlockHead = list;
}
else
{
// Find the last block and append the list
TBlockType * tail = HeapBlockList::Tail(currentHeapBlockList);
Assert(tail != nullptr);
tail->SetNextBlock(list);
// If we are not currently allocating from the existing heapBlockList,
// that means fill all the exiting one already, we should start with what we just appended.
if (this->nextAllocableBlockHead == nullptr)
{
this->nextAllocableBlockHead = list;
}
}
}
template <typename TBlockType>
void
HeapBucketT<TBlockType>::EnumerateObjects(ObjectInfoBits infoBits, void (*CallBackFunction)(void * address, size_t size))
{
UpdateAllocators();
HeapBucket::EnumerateObjects(fullBlockList, infoBits, CallBackFunction);
#if ENABLE_ALLOCATIONS_DURING_CONCURRENT_SWEEP && SUPPORT_WIN32_SLIST
if (CONFIG_FLAG_RELEASE(EnableConcurrentSweepAlloc) && !this->IsAnyFinalizableBucket())
{
HeapBucket::EnumerateObjects(sweepableHeapBlockList, infoBits, CallBackFunction);
}
#endif
HeapBucket::EnumerateObjects(heapBlockList, infoBits, CallBackFunction);
}
#ifdef RECYCLER_SLOW_CHECK_ENABLED
template <typename TBlockType>
void
HeapBucketT<TBlockType>::VerifyHeapBlockCount(bool background)
{
// TODO-REFACTOR: GetNonEmptyHeapBlockCount really should be virtual
static_cast<typename SmallHeapBlockType<TBlockType::RequiredAttributes, typename TBlockType::HeapBlockAttributes>::BucketType *>(this)->GetNonEmptyHeapBlockCount(true);
if (!background)
{
this->GetEmptyHeapBlockCount();
}
}
template <typename TBlockType>
size_t
HeapBucketT<TBlockType>::Check(bool checkCount)
{
Assert(this->GetRecycler()->recyclerSweepManager == nullptr);
UpdateAllocators();
size_t smallHeapBlockCount = HeapInfo::Check(true, false, this->fullBlockList);
bool allocatingDuringConcurrentSweep = false;
#if ENABLE_ALLOCATIONS_DURING_CONCURRENT_SWEEP && SUPPORT_WIN32_SLIST
if (CONFIG_FLAG_RELEASE(EnableConcurrentSweepAlloc) && !this->IsAnyFinalizableBucket())
{
allocatingDuringConcurrentSweep = true;
// This lock is needed only in the debug mode while we verify block counts. Not needed otherwise, as this list is never accessed concurrently.
// Items are added to it by the allocator when allocations are allowed during concurrent sweep. The list is drained during the next sweep while
// allocation are stopped.
debugSweepableHeapBlockListLock.Enter();
smallHeapBlockCount += HeapInfo::Check(true, false, this->sweepableHeapBlockList);
debugSweepableHeapBlockListLock.Leave();
}
#endif
smallHeapBlockCount += HeapInfo::Check(true, false, this->heapBlockList, this->nextAllocableBlockHead);
smallHeapBlockCount += HeapInfo::Check(false, false, this->nextAllocableBlockHead);
Assert(!checkCount || this->heapBlockCount == smallHeapBlockCount || (this->heapBlockCount >= 65535 && allocatingDuringConcurrentSweep));
return smallHeapBlockCount;
}
#endif
#if ENABLE_MEM_STATS
template <typename TBlockType>
void
HeapBucketT<TBlockType>::AggregateBucketStats()
{
HeapBucket::AggregateBucketStats(); // call super
auto allocatorHead = &this->allocatorHead;
auto allocatorCurr = allocatorHead;
do
{
TBlockType* allocatorHeapBlock = allocatorCurr->GetHeapBlock();
if (allocatorHeapBlock)
{
allocatorHeapBlock->AggregateBlockStats(this->memStats, true, allocatorCurr->freeObjectList, allocatorCurr->endAddress != 0);
}
allocatorCurr = allocatorCurr->GetNext();
} while (allocatorCurr != allocatorHead);
auto blockStatsAggregator = [this](TBlockType* heapBlock) {
heapBlock->AggregateBlockStats(this->memStats);
};
HeapBlockList::ForEach(fullBlockList, blockStatsAggregator);
#if ENABLE_ALLOCATIONS_DURING_CONCURRENT_SWEEP && SUPPORT_WIN32_SLIST
if (CONFIG_FLAG_RELEASE(EnableConcurrentSweepAlloc) && !this->IsAnyFinalizableBucket())
{
HeapBlockList::ForEach(sweepableHeapBlockList, blockStatsAggregator);
}
#endif
HeapBlockList::ForEach(heapBlockList, blockStatsAggregator);
}
#endif
#ifdef RECYCLER_MEMORY_VERIFY
template <typename TBlockType>
void
HeapBucketT<TBlockType>::Verify()
{
UpdateAllocators();
#if DBG
RecyclerVerifyListConsistencyData recyclerVerifyListConsistencyData;
if (TBlockType::HeapBlockAttributes::IsSmallBlock)
{
recyclerVerifyListConsistencyData.smallBlockVerifyListConsistencyData.SetupVerifyListConsistencyData((SmallHeapBlock*) nullptr, true, false);
}
else if (TBlockType::HeapBlockAttributes::IsMediumBlock)
{
recyclerVerifyListConsistencyData.mediumBlockVerifyListConsistencyData.SetupVerifyListConsistencyData((MediumHeapBlock*) nullptr, true, false);
}
else
{
Assert(false);
}
#endif
HeapBlockList::ForEach(fullBlockList, [DebugOnly(&recyclerVerifyListConsistencyData)](TBlockType * heapBlock)
{
DebugOnly(VerifyBlockConsistencyInList(heapBlock, recyclerVerifyListConsistencyData));
heapBlock->Verify();
});
#if ENABLE_ALLOCATIONS_DURING_CONCURRENT_SWEEP && SUPPORT_WIN32_SLIST
if (CONFIG_FLAG_RELEASE(EnableConcurrentSweepAlloc) && !this->IsAnyFinalizableBucket())
{
#if DBG
if (TBlockType::HeapBlockAttributes::IsSmallBlock)
{
recyclerVerifyListConsistencyData.smallBlockVerifyListConsistencyData.SetupVerifyListConsistencyData((SmallHeapBlock*) nullptr, true, false);
}
else if (TBlockType::HeapBlockAttributes::IsMediumBlock)
{
recyclerVerifyListConsistencyData.mediumBlockVerifyListConsistencyData.SetupVerifyListConsistencyData((MediumHeapBlock*) nullptr, true, false);
}
else
{
Assert(false);
}
#endif
HeapBlockList::ForEach(sweepableHeapBlockList, [DebugOnly(&recyclerVerifyListConsistencyData)](TBlockType * heapBlock)
{
DebugOnly(VerifyBlockConsistencyInList(heapBlock, recyclerVerifyListConsistencyData));
heapBlock->Verify();
});
}
#endif
#if DBG
if (TBlockType::HeapBlockAttributes::IsSmallBlock)
{
recyclerVerifyListConsistencyData.smallBlockVerifyListConsistencyData.SetupVerifyListConsistencyData((SmallHeapBlock*) this->nextAllocableBlockHead, true, false);
}
else if (TBlockType::HeapBlockAttributes::IsMediumBlock)
{
recyclerVerifyListConsistencyData.mediumBlockVerifyListConsistencyData.SetupVerifyListConsistencyData((MediumHeapBlock*) this->nextAllocableBlockHead, true, false);
}
else
{
Assert(false);
}
#endif
HeapBlockList::ForEach(heapBlockList, [this, DebugOnly(&recyclerVerifyListConsistencyData)](TBlockType * heapBlock)
{
DebugOnly(VerifyBlockConsistencyInList(heapBlock, recyclerVerifyListConsistencyData));
char * bumpAllocateAddress = nullptr;
this->ForEachAllocator([heapBlock, &bumpAllocateAddress](TBlockAllocatorType * allocator)
{
if (allocator->GetHeapBlock() == heapBlock && allocator->GetEndAddress() != nullptr)
{
Assert(bumpAllocateAddress == nullptr);
bumpAllocateAddress = (char *)allocator->GetFreeObjectList();
}
});
if (bumpAllocateAddress != nullptr)
{
heapBlock->VerifyBumpAllocated(bumpAllocateAddress);
}
else
{
heapBlock->Verify(false);
}
});
}
#endif
#ifdef RECYCLER_VERIFY_MARK
template <typename TBlockType>
void
HeapBucketT<TBlockType>::VerifyMark()
{
HeapBlockList::ForEach(this->fullBlockList, [](TBlockType * heapBlock)
{
heapBlock->VerifyMark();
});
#if ENABLE_ALLOCATIONS_DURING_CONCURRENT_SWEEP && SUPPORT_WIN32_SLIST
if (CONFIG_FLAG_RELEASE(EnableConcurrentSweepAlloc) && !this->IsAnyFinalizableBucket())
{
HeapBlockList::ForEach(this->sweepableHeapBlockList, [](TBlockType * heapBlock)
{
heapBlock->VerifyMark();
});
}
#endif
HeapBlockList::ForEach(this->heapBlockList, [](TBlockType * heapBlock)
{
heapBlock->VerifyMark();
});
}
#endif
template <class TBlockAttributes>
void
HeapBucketGroup<TBlockAttributes>::Initialize(HeapInfo * heapInfo, uint sizeCat)
{
heapBucket.Initialize(heapInfo, sizeCat);
leafHeapBucket.Initialize(heapInfo, sizeCat);
#ifdef RECYCLER_WRITE_BARRIER
smallNormalWithBarrierHeapBucket.Initialize(heapInfo, sizeCat);
smallFinalizableWithBarrierHeapBucket.Initialize(heapInfo, sizeCat);
#endif
finalizableHeapBucket.Initialize(heapInfo, sizeCat);
#ifdef RECYCLER_VISITED_HOST
recyclerVisitedHostHeapBucket.Initialize(heapInfo, sizeCat);
#endif
}
template <class TBlockAttributes>
void
HeapBucketGroup<TBlockAttributes>::ResetMarks(ResetMarkFlags flags)
{
heapBucket.ResetMarks(flags);
leafHeapBucket.ResetMarks(flags);
#ifdef RECYCLER_WRITE_BARRIER
smallNormalWithBarrierHeapBucket.ResetMarks(flags);
smallFinalizableWithBarrierHeapBucket.ResetMarks(flags);
#endif
// Although we pass in premarkFreeObjects, the finalizable heap bucket ignores
// this parameter and never pre-marks free objects
finalizableHeapBucket.ResetMarks(flags);
#ifdef RECYCLER_VISITED_HOST
recyclerVisitedHostHeapBucket.ResetMarks(flags);
#endif
}
template <class TBlockAttributes>
void
HeapBucketGroup<TBlockAttributes>::ScanInitialImplicitRoots(Recycler * recycler)
{
heapBucket.ScanInitialImplicitRoots(recycler);
// Don't need to scan implicit roots on leaf heap bucket
#ifdef RECYCLER_WRITE_BARRIER
smallNormalWithBarrierHeapBucket.ScanInitialImplicitRoots(recycler);
smallFinalizableWithBarrierHeapBucket.ScanInitialImplicitRoots(recycler);
#endif
finalizableHeapBucket.ScanInitialImplicitRoots(recycler);
}
template <class TBlockAttributes>
void
HeapBucketGroup<TBlockAttributes>::ScanNewImplicitRoots(Recycler * recycler)
{
heapBucket.ScanNewImplicitRoots(recycler);
// Need to scan new implicit roots on leaf heap bucket
leafHeapBucket.ScanNewImplicitRoots(recycler);
#ifdef RECYCLER_WRITE_BARRIER
smallNormalWithBarrierHeapBucket.ScanNewImplicitRoots(recycler);
smallFinalizableWithBarrierHeapBucket.ScanNewImplicitRoots(recycler);
#endif
finalizableHeapBucket.ScanNewImplicitRoots(recycler);
}
template <class TBlockAttributes>
void
HeapBucketGroup<TBlockAttributes>::Sweep(RecyclerSweep& recyclerSweep)
{
heapBucket.Sweep(recyclerSweep);
leafHeapBucket.Sweep(recyclerSweep);
#ifdef RECYCLER_WRITE_BARRIER
smallNormalWithBarrierHeapBucket.Sweep(recyclerSweep);
#endif
}
// Sweep finalizable objects first to ensure that if they reference any other
// objects in the finalizer - they are valid
template <class TBlockAttributes>
void
HeapBucketGroup<TBlockAttributes>::SweepFinalizableObjects(RecyclerSweep& recyclerSweep)
{
finalizableHeapBucket.Sweep(recyclerSweep);
#ifdef RECYCLER_VISITED_HOST
recyclerVisitedHostHeapBucket.Sweep(recyclerSweep);
#endif
#ifdef RECYCLER_WRITE_BARRIER
smallFinalizableWithBarrierHeapBucket.Sweep(recyclerSweep);
#endif
}
template <class TBlockAttributes>
void
HeapBucketGroup<TBlockAttributes>::DisposeObjects()
{
finalizableHeapBucket.DisposeObjects();
#ifdef RECYCLER_VISITED_HOST
recyclerVisitedHostHeapBucket.DisposeObjects();
#endif
#ifdef RECYCLER_WRITE_BARRIER
smallFinalizableWithBarrierHeapBucket.DisposeObjects();
#endif
}
template <class TBlockAttributes>
void
HeapBucketGroup<TBlockAttributes>::TransferDisposedObjects()
{
finalizableHeapBucket.TransferDisposedObjects();
#ifdef RECYCLER_VISITED_HOST
recyclerVisitedHostHeapBucket.TransferDisposedObjects();
#endif
#ifdef RECYCLER_WRITE_BARRIER
smallFinalizableWithBarrierHeapBucket.TransferDisposedObjects();
#endif
}
template <class TBlockAttributes>
void
HeapBucketGroup<TBlockAttributes>::EnumerateObjects(ObjectInfoBits infoBits, void (*CallBackFunction)(void * address, size_t size))
{
heapBucket.EnumerateObjects(infoBits, CallBackFunction);
leafHeapBucket.EnumerateObjects(infoBits, CallBackFunction);
#ifdef RECYCLER_WRITE_BARRIER
smallNormalWithBarrierHeapBucket.EnumerateObjects(infoBits, CallBackFunction);
smallFinalizableWithBarrierHeapBucket.EnumerateObjects(infoBits, CallBackFunction);
#endif
finalizableHeapBucket.EnumerateObjects(infoBits, CallBackFunction);
#ifdef RECYCLER_VISITED_HOST
recyclerVisitedHostHeapBucket.EnumerateObjects(infoBits, CallBackFunction);
#endif
}
template <class TBlockAttributes>
void
HeapBucketGroup<TBlockAttributes>::FinalizeAllObjects()
{
finalizableHeapBucket.FinalizeAllObjects();
#ifdef RECYCLER_VISITED_HOST
recyclerVisitedHostHeapBucket.FinalizeAllObjects();
#endif
#ifdef RECYCLER_WRITE_BARRIER
smallFinalizableWithBarrierHeapBucket.FinalizeAllObjects();
#endif
}
template <class TBlockAttributes>
uint
HeapBucketGroup<TBlockAttributes>::Rescan(Recycler * recycler, RescanFlags flags)
{
return heapBucket.Rescan(recycler, flags) +
leafHeapBucket.Rescan(recycler, flags) +
#ifdef RECYCLER_WRITE_BARRIER
smallNormalWithBarrierHeapBucket.Rescan(recycler, flags) +
smallFinalizableWithBarrierHeapBucket.Rescan(recycler, flags) +
#endif
#ifdef RECYCLER_VISITED_HOST
recyclerVisitedHostHeapBucket.Rescan(recycler, flags) +
#endif
finalizableHeapBucket.Rescan(recycler, flags);
}
#if ENABLE_CONCURRENT_GC
template <class TBlockAttributes>
void
HeapBucketGroup<TBlockAttributes>::PrepareSweep()
{
heapBucket.PrepareSweep();
leafHeapBucket.PrepareSweep();
#ifdef RECYCLER_WRITE_BARRIER
smallNormalWithBarrierHeapBucket.PrepareSweep();
smallFinalizableWithBarrierHeapBucket.PrepareSweep();
#endif
finalizableHeapBucket.PrepareSweep();
#ifdef RECYCLER_VISITED_HOST
recyclerVisitedHostHeapBucket.PrepareSweep();
#endif
}
template <class TBlockAttributes>
void
HeapBucketGroup<TBlockAttributes>::SetupBackgroundSweep(RecyclerSweep& recyclerSweep)
{
heapBucket.SetupBackgroundSweep(recyclerSweep);
leafHeapBucket.SetupBackgroundSweep(recyclerSweep);
#ifdef RECYCLER_WRITE_BARRIER
smallNormalWithBarrierHeapBucket.SetupBackgroundSweep(recyclerSweep);
#endif
}
#endif
#if ENABLE_PARTIAL_GC
template <class TBlockAttributes>
void
HeapBucketGroup<TBlockAttributes>::SweepPartialReusePages(RecyclerSweep& recyclerSweep)
{
// Leaf heap bucket are always reused for allocation and can be done on the concurrent thread
// WriteBarrier-TODO: Do the same for write barrier buckets
heapBucket.SweepPartialReusePages(recyclerSweep);
#ifdef RECYCLER_WRITE_BARRIER
smallNormalWithBarrierHeapBucket.SweepPartialReusePages(recyclerSweep);
smallFinalizableWithBarrierHeapBucket.SweepPartialReusePages(recyclerSweep);
#endif
finalizableHeapBucket.SweepPartialReusePages(recyclerSweep);
#ifdef RECYCLER_VISITED_HOST
recyclerVisitedHostHeapBucket.SweepPartialReusePages(recyclerSweep);
#endif
}
template <class TBlockAttributes>
void
HeapBucketGroup<TBlockAttributes>::FinishPartialCollect(RecyclerSweep * recyclerSweep)
{
heapBucket.FinishPartialCollect(recyclerSweep);
#ifdef RECYCLER_WRITE_BARRIER
smallNormalWithBarrierHeapBucket.FinishPartialCollect(recyclerSweep);
smallFinalizableWithBarrierHeapBucket.FinishPartialCollect(recyclerSweep);
#endif
finalizableHeapBucket.FinishPartialCollect(recyclerSweep);
#ifdef RECYCLER_VISITED_HOST
recyclerVisitedHostHeapBucket.FinishPartialCollect(recyclerSweep);
#endif
// Leaf heap block always do a full sweep instead of partial sweep
// (since touching the page doesn't affect rescan)
// So just need to verify heap block count (which finishPartialCollect would have done)
// WriteBarrier-TODO: Do that same for write barrier buckets
RECYCLER_SLOW_CHECK(leafHeapBucket.VerifyHeapBlockCount(recyclerSweep != nullptr && recyclerSweep->IsBackground()));
}
#endif
#if ENABLE_CONCURRENT_GC
template <class TBlockAttributes>
void
HeapBucketGroup<TBlockAttributes>::SweepPendingObjects(RecyclerSweep& recyclerSweep)
{
// For leaf buckets, we can always reuse the page as we don't need to rescan them for partial GC
// It should have been swept immediately during Sweep
// WriteBarrier-TODO: Do the same for write barrier buckets
Assert(recyclerSweep.GetPendingSweepBlockList(&leafHeapBucket) == nullptr);
heapBucket.SweepPendingObjects(recyclerSweep);
#ifdef RECYCLER_WRITE_BARRIER
smallNormalWithBarrierHeapBucket.SweepPendingObjects(recyclerSweep);
smallFinalizableWithBarrierHeapBucket.SweepPendingObjects(recyclerSweep);
#endif
finalizableHeapBucket.SweepPendingObjects(recyclerSweep);
#ifdef RECYCLER_VISITED_HOST
recyclerVisitedHostHeapBucket.SweepPendingObjects(recyclerSweep);
#endif
}
template <class TBlockAttributes>
void
HeapBucketGroup<TBlockAttributes>::TransferPendingEmptyHeapBlocks(RecyclerSweep& recyclerSweep)
{
recyclerSweep.TransferPendingEmptyHeapBlocks(&heapBucket);
recyclerSweep.TransferPendingEmptyHeapBlocks(&leafHeapBucket);
#ifdef RECYCLER_WRITE_BARRIER
recyclerSweep.TransferPendingEmptyHeapBlocks(&smallNormalWithBarrierHeapBucket);
recyclerSweep.TransferPendingEmptyHeapBlocks(&smallFinalizableWithBarrierHeapBucket);
#endif
recyclerSweep.TransferPendingEmptyHeapBlocks(&finalizableHeapBucket);
#ifdef RECYCLER_VISITED_HOST
recyclerSweep.TransferPendingEmptyHeapBlocks(&recyclerVisitedHostHeapBucket);
#endif
}
#endif
#if DBG || defined(RECYCLER_SLOW_CHECK_ENABLED)
template <class TBlockAttributes>
size_t
HeapBucketGroup<TBlockAttributes>::GetNonEmptyHeapBlockCount(bool checkCount) const
{
return heapBucket.GetNonEmptyHeapBlockCount(checkCount) +
finalizableHeapBucket.GetNonEmptyHeapBlockCount(checkCount) +
#ifdef RECYCLER_VISITED_HOST
recyclerVisitedHostHeapBucket.GetNonEmptyHeapBlockCount(checkCount) +
#endif
#ifdef RECYCLER_WRITE_BARRIER
smallNormalWithBarrierHeapBucket.GetNonEmptyHeapBlockCount(checkCount) +
smallFinalizableWithBarrierHeapBucket.GetNonEmptyHeapBlockCount(checkCount) +
#endif
leafHeapBucket.GetNonEmptyHeapBlockCount(checkCount);
}
template <class TBlockAttributes>
size_t
HeapBucketGroup<TBlockAttributes>::GetEmptyHeapBlockCount() const
{
return heapBucket.GetEmptyHeapBlockCount() +
finalizableHeapBucket.GetEmptyHeapBlockCount() +
#ifdef RECYCLER_VISITED_HOST
recyclerVisitedHostHeapBucket.GetEmptyHeapBlockCount() +
#endif
#ifdef RECYCLER_WRITE_BARRIER
smallNormalWithBarrierHeapBucket.GetEmptyHeapBlockCount() +
smallFinalizableWithBarrierHeapBucket.GetEmptyHeapBlockCount() +
#endif
leafHeapBucket.GetEmptyHeapBlockCount();
}
#endif
#ifdef RECYCLER_SLOW_CHECK_ENABLED
template <class TBlockAttributes>
size_t
HeapBucketGroup<TBlockAttributes>::Check()
{
return heapBucket.Check() + finalizableHeapBucket.Check() + leafHeapBucket.Check()
#ifdef RECYCLER_VISITED_HOST
+ recyclerVisitedHostHeapBucket.Check()
#endif
#ifdef RECYCLER_WRITE_BARRIER
+ smallNormalWithBarrierHeapBucket.Check() + smallFinalizableWithBarrierHeapBucket.Check()
#endif
;
}
#endif
#ifdef RECYCLER_MEMORY_VERIFY
template <class TBlockAttributes>
void
HeapBucketGroup<TBlockAttributes>::Verify()
{
heapBucket.Verify();
finalizableHeapBucket.Verify();
#ifdef RECYCLER_VISITED_HOST
recyclerVisitedHostHeapBucket.Verify();
#endif
leafHeapBucket.Verify();
#ifdef RECYCLER_WRITE_BARRIER
smallNormalWithBarrierHeapBucket.Verify();
smallFinalizableWithBarrierHeapBucket.Verify();
#endif
}
#endif
#ifdef RECYCLER_VERIFY_MARK
template <class TBlockAttributes>
void
HeapBucketGroup<TBlockAttributes>::VerifyMark()
{
heapBucket.VerifyMark();
finalizableHeapBucket.VerifyMark();
#ifdef RECYCLER_VISITED_HOST
recyclerVisitedHostHeapBucket.VerifyMark();
#endif
leafHeapBucket.VerifyMark();
#ifdef RECYCLER_WRITE_BARRIER
smallNormalWithBarrierHeapBucket.VerifyMark();
smallFinalizableWithBarrierHeapBucket.VerifyMark();
#endif
}
#endif
#if ENABLE_ALLOCATIONS_DURING_CONCURRENT_SWEEP
template <class TBlockAttributes>
void
HeapBucketGroup<TBlockAttributes>::StartAllocationDuringConcurrentSweep()
{
// If there were no allocable heap blocks we would not have started alllocations. Stop allocations, only if we started allocations for each of these buckets.
if (heapBucket.IsAllocationStopped())
{
heapBucket.StartAllocationDuringConcurrentSweep();
}
if (leafHeapBucket.IsAllocationStopped())
{
leafHeapBucket.StartAllocationDuringConcurrentSweep();
}
#ifdef RECYCLER_WRITE_BARRIER
if (smallNormalWithBarrierHeapBucket.IsAllocationStopped())
{
smallNormalWithBarrierHeapBucket.StartAllocationDuringConcurrentSweep();
}
#endif
}
template <class TBlockAttributes>
bool
HeapBucketGroup<TBlockAttributes>::DoTwoPassConcurrentSweepPreCheck()
{
return heapBucket.DoTwoPassConcurrentSweepPreCheck() ||
leafHeapBucket.DoTwoPassConcurrentSweepPreCheck()
#ifdef RECYCLER_WRITE_BARRIER
|| smallNormalWithBarrierHeapBucket.DoTwoPassConcurrentSweepPreCheck();
#endif
}
template <class TBlockAttributes>
void
HeapBucketGroup<TBlockAttributes>::FinishConcurrentSweepPass1(RecyclerSweep& recyclerSweep)
{
heapBucket.FinishConcurrentSweepPass1(recyclerSweep);
leafHeapBucket.FinishConcurrentSweepPass1(recyclerSweep);
#ifdef RECYCLER_WRITE_BARRIER
smallNormalWithBarrierHeapBucket.FinishConcurrentSweepPass1(recyclerSweep);
#endif
}
template <class TBlockAttributes>
void
HeapBucketGroup<TBlockAttributes>::FinishSweepPrep(RecyclerSweep& recyclerSweep)
{
heapBucket.FinishSweepPrep(recyclerSweep);
leafHeapBucket.FinishSweepPrep(recyclerSweep);
#ifdef RECYCLER_WRITE_BARRIER
smallNormalWithBarrierHeapBucket.FinishSweepPrep(recyclerSweep);
#endif
}
template <class TBlockAttributes>
void
HeapBucketGroup<TBlockAttributes>::FinishConcurrentSweep()
{
heapBucket.FinishConcurrentSweep();
leafHeapBucket.FinishConcurrentSweep();
#ifdef RECYCLER_WRITE_BARRIER
smallNormalWithBarrierHeapBucket.FinishConcurrentSweep();
#endif
}
#endif
#if DBG
template <class TBlockAttributes>
bool
HeapBucketGroup<TBlockAttributes>::AllocatorsAreEmpty()
{
return heapBucket.AllocatorsAreEmpty()
&& finalizableHeapBucket.AllocatorsAreEmpty()
#ifdef RECYCLER_VISITED_HOST
&& recyclerVisitedHostHeapBucket.AllocatorsAreEmpty()
#endif
&& leafHeapBucket.AllocatorsAreEmpty()
#ifdef RECYCLER_WRITE_BARRIER
&& smallNormalWithBarrierHeapBucket.AllocatorsAreEmpty()
&& smallFinalizableWithBarrierHeapBucket.AllocatorsAreEmpty()
#endif
;
}
#endif
namespace Memory
{
template class HeapBucketGroup<SmallAllocationBlockAttributes>;
template class HeapBucketGroup<MediumAllocationBlockAttributes>;
EXPLICIT_INSTANTIATE_WITH_SMALL_HEAP_BLOCK_TYPE(HeapBucketT);
};