blob: 4da2a17274f19a5bffec3b3c45be103f6b916b15 [file]
//-------------------------------------------------------------------------------------------------------
// Copyright (C) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE.txt file in the project root for full license information.
//-------------------------------------------------------------------------------------------------------
template <typename T>
class PageStack
{
private:
struct Chunk : public PagePoolPage
{
Chunk * nextChunk;
T entries[];
};
static const size_t EntriesPerChunk = (AutoSystemInfo::PageSize - sizeof(Chunk)) / sizeof(T);
public:
PageStack(PagePool * pagePool);
~PageStack();
void Init(uint reservedPageCount = 0);
void Clear();
bool Pop(T * item);
bool Push(T item);
uint Split(uint targetCount, __in_ecount(targetCount) PageStack<T> ** targetStacks);
void Abort();
void Release();
bool IsEmpty() const;
#if DBG
bool HasChunk() const
{
return this->currentChunk != nullptr;
}
#endif
#ifdef ENABLE_DEBUG_CONFIG_OPTIONS
void SetMaxPageCount(size_t maxPageCount)
{
this->maxPageCount = maxPageCount > 1 ? maxPageCount : 1;
}
#endif
static const uint MaxSplitTargets = 3; // Not counting original stack, so this supports 4-way parallel
private:
Chunk * CreateChunk();
void FreeChunk(Chunk * chunk);
private:
T * nextEntry;
T * chunkStart;
T * chunkEnd;
Chunk * currentChunk;
PagePool * pagePool;
bool usesReservedPages;
#if DBG
size_t count;
#endif
#ifdef ENABLE_DEBUG_CONFIG_OPTIONS
size_t pageCount;
size_t maxPageCount;
#endif
};
template <typename T>
inline
bool PageStack<T>::Pop(T * item)
{
Assert(currentChunk != nullptr);
if (nextEntry == chunkStart)
{
// We're at the beginning of the chunk. Move to the previous chunk, if any
if (currentChunk->nextChunk == nullptr)
{
// All done
Assert(count == 0);
return false;
}
Chunk * temp = currentChunk;
currentChunk = currentChunk->nextChunk;
FreeChunk(temp);
chunkStart = currentChunk->entries;
chunkEnd = &currentChunk->entries[EntriesPerChunk];
nextEntry = chunkEnd;
}
Assert(nextEntry > chunkStart && nextEntry <= chunkEnd);
nextEntry--;
*item = *nextEntry;
#if DBG
count--;
Assert(count == (nextEntry - chunkStart) + (pageCount - 1) * EntriesPerChunk);
#endif
return true;
}
template <typename T>
inline
bool PageStack<T>::Push(T item)
{
if (nextEntry == chunkEnd)
{
Chunk * newChunk = CreateChunk();
if (newChunk == nullptr)
{
return false;
}
newChunk->nextChunk = currentChunk;
currentChunk = newChunk;
chunkStart = currentChunk->entries;
chunkEnd = &currentChunk->entries[EntriesPerChunk];
nextEntry = chunkStart;
}
Assert(nextEntry >= chunkStart && nextEntry < chunkEnd);
*nextEntry = item;
nextEntry++;
#if DBG
count++;
Assert(count == (nextEntry - chunkStart) + (pageCount - 1) * EntriesPerChunk);
#endif
return true;
}
template <typename T>
PageStack<T>::PageStack(PagePool * pagePool) :
pagePool(pagePool),
currentChunk(nullptr),
nextEntry(nullptr),
chunkStart(nullptr),
chunkEnd(nullptr),
usesReservedPages(false)
{
#ifdef ENABLE_DEBUG_CONFIG_OPTIONS
pageCount = 0;
maxPageCount = (size_t)-1; // Default to no limit
#endif
#if DBG
count = 0;
#endif
}
template <typename T>
PageStack<T>::~PageStack()
{
Assert(currentChunk == nullptr);
Assert(nextEntry == nullptr);
Assert(count == 0);
Assert(pageCount == 0);
}
template <typename T>
void PageStack<T>::Init(uint reservedPageCount)
{
if (reservedPageCount > 0)
{
this->usesReservedPages = true;
this->pagePool->ReservePages(reservedPageCount);
}
// Preallocate one chunk.
Assert(currentChunk == nullptr);
currentChunk = CreateChunk();
if (currentChunk == nullptr)
{
Js::Throw::OutOfMemory();
}
currentChunk->nextChunk = nullptr;
chunkStart = currentChunk->entries;
chunkEnd = &currentChunk->entries[EntriesPerChunk];
nextEntry = chunkStart;
}
template <typename T>
void PageStack<T>::Clear()
{
currentChunk = nullptr;
nextEntry = nullptr;
#if DBG
count = 0;
pageCount = 0;
#endif
}
template <typename T>
typename PageStack<T>::Chunk * PageStack<T>::CreateChunk()
{
#ifdef ENABLE_DEBUG_CONFIG_OPTIONS
if (pageCount >= maxPageCount)
{
return nullptr;
}
#endif
Chunk * newChunk = (Chunk *)this->pagePool->GetPage(usesReservedPages);
if (newChunk == nullptr)
{
return nullptr;
}
#ifdef ENABLE_DEBUG_CONFIG_OPTIONS
pageCount++;
#endif
return newChunk;
}
template <typename T>
void PageStack<T>::FreeChunk(Chunk * chunk)
{
#ifdef ENABLE_DEBUG_CONFIG_OPTIONS
pageCount--;
#endif
this->pagePool->FreePage(chunk);
}
template <typename T>
uint PageStack<T>::Split(uint targetCount, __in_ecount(targetCount) PageStack<T> ** targetStacks)
{
// Split the current stack up to [targetCount + 1] ways.
// [targetStacks] contains the target stacks and must have [targetCount] elements.
Assert(targetCount > 0 && targetCount <= MaxSplitTargets);
Assert(targetStacks);
__analysis_assume(targetCount <= MaxSplitTargets);
Chunk * mainCurrent;
Chunk * targetCurrents[MaxSplitTargets];
// Do the initial split of first pages for each target stack.
// During this, if we run out of pages, we will return a value < maxSplit to
// indicate that the split was less than the maximum possible.
Chunk * chunk = this->currentChunk;
Assert(chunk != nullptr);
// The first chunk is assigned to the main stack, and since it's already there,
// we just advance to the next chunk and start assigning to each target stack.
mainCurrent = chunk;
chunk = chunk->nextChunk;
uint targetIndex = 0;
while (targetIndex < targetCount)
{
if (chunk == nullptr)
{
// No more pages. Adjust targetCount down to what we were actually able to do.
// We'll return this number below so the caller knows.
targetCount = targetIndex;
break;
}
// Target stack should be empty.
// If it has a free page currently, release it.
Assert(targetStacks[targetIndex]->IsEmpty());
targetStacks[targetIndex]->Release();
targetStacks[targetIndex]->currentChunk = chunk;
targetStacks[targetIndex]->chunkStart = chunk->entries;
targetStacks[targetIndex]->chunkEnd = &chunk->entries[EntriesPerChunk];
targetStacks[targetIndex]->nextEntry = targetStacks[targetIndex]->chunkEnd;
#ifdef ENABLE_DEBUG_CONFIG_OPTIONS
this->pageCount--;
targetStacks[targetIndex]->pageCount = 1;
#endif
#if DBG
this->count -= EntriesPerChunk;
targetStacks[targetIndex]->count = EntriesPerChunk;
#endif
targetCurrents[targetIndex] = chunk;
chunk = chunk->nextChunk;
targetIndex++;
}
// Loop through the remaining chunks (if any),
// assigning each chunk to the main chunk and the target chunks in turn,
// and linking each chunk to the end of the respective list.
while (true)
{
if (chunk == nullptr)
{
break;
}
mainCurrent->nextChunk = chunk;
mainCurrent = chunk;
chunk = chunk->nextChunk;
targetIndex = 0;
while (targetIndex < targetCount)
{
if (chunk == nullptr)
{
break;
}
targetCurrents[targetIndex]->nextChunk = chunk;
targetCurrents[targetIndex] = chunk;
#ifdef ENABLE_DEBUG_CONFIG_OPTIONS
this->pageCount--;
targetStacks[targetIndex]->pageCount++;
#endif
#if DBG
this->count -= EntriesPerChunk;
targetStacks[targetIndex]->count += EntriesPerChunk;
#endif
chunk = chunk->nextChunk;
targetIndex++;
}
}
// Terminate all the split chunk lists with null
mainCurrent->nextChunk = nullptr;
targetIndex = 0;
while (targetIndex < targetCount)
{
targetCurrents[targetIndex]->nextChunk = nullptr;
targetIndex++;
}
// Return the actual split count we were able to do, which may have been lowered above.
return targetCount;
}
template <typename T>
void PageStack<T>::Abort()
{
// Abandon the current entries in the stack and reset to initialized state.
if (currentChunk == nullptr)
{
Assert(count == 0);
return;
}
// Free all the chunks except the first one
while (currentChunk->nextChunk != nullptr)
{
Chunk * temp = currentChunk;
currentChunk = currentChunk->nextChunk;
FreeChunk(temp);
}
chunkStart = currentChunk->entries;
chunkEnd = &currentChunk->entries[EntriesPerChunk];
nextEntry = chunkStart;
#if DBG
count = 0;
#endif
}
template <typename T>
void PageStack<T>::Release()
{
Assert(IsEmpty());
// We may have a preallocated chunk still held; if so release it.
if (currentChunk != nullptr)
{
Assert(currentChunk->nextChunk == nullptr);
FreeChunk(currentChunk);
currentChunk = nullptr;
}
nextEntry = nullptr;
chunkStart = nullptr;
chunkEnd = nullptr;
}
template <typename T>
bool PageStack<T>::IsEmpty() const
{
if (currentChunk == nullptr)
{
Assert(count == 0);
Assert(nextEntry == nullptr);
return true;
}
if (nextEntry == chunkStart && currentChunk->nextChunk == nullptr)
{
Assert(count == 0);
return true;
}
Assert(count != 0);
return false;
}