265 lines
10 KiB
C++
265 lines
10 KiB
C++
// Copyright 2019 The Dawn Authors
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
#include "dawn_native/BuddyAllocator.h"
|
|
|
|
#include "common/Assert.h"
|
|
#include "common/Math.h"
|
|
|
|
namespace dawn_native {
|
|
|
|
BuddyAllocator::BuddyAllocator(uint64_t maxSize) : mMaxBlockSize(maxSize) {
|
|
ASSERT(IsPowerOfTwo(maxSize));
|
|
|
|
mFreeLists.resize(Log2(mMaxBlockSize) + 1);
|
|
|
|
// Insert the level0 free block.
|
|
mRoot = new BuddyBlock(maxSize, /*offset*/ 0);
|
|
mFreeLists[0] = {mRoot};
|
|
}
|
|
|
|
BuddyAllocator::~BuddyAllocator() {
|
|
if (mRoot) {
|
|
DeleteBlock(mRoot);
|
|
}
|
|
}
|
|
|
|
uint64_t BuddyAllocator::ComputeTotalNumOfFreeBlocksForTesting() const {
|
|
return ComputeNumOfFreeBlocks(mRoot);
|
|
}
|
|
|
|
uint64_t BuddyAllocator::ComputeNumOfFreeBlocks(BuddyBlock* block) const {
|
|
if (block->mState == BlockState::Free) {
|
|
return 1;
|
|
} else if (block->mState == BlockState::Split) {
|
|
return ComputeNumOfFreeBlocks(block->split.pLeft) +
|
|
ComputeNumOfFreeBlocks(block->split.pLeft->pBuddy);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
uint32_t BuddyAllocator::ComputeLevelFromBlockSize(uint64_t blockSize) const {
|
|
// Every level in the buddy system can be indexed by order-n where n = log2(blockSize).
|
|
// However, mFreeList zero-indexed by level.
|
|
// For example, blockSize=4 is Level1 if MAX_BLOCK is 8.
|
|
return Log2(mMaxBlockSize) - Log2(blockSize);
|
|
}
|
|
|
|
uint64_t BuddyAllocator::GetNextFreeAlignedBlock(size_t allocationBlockLevel,
|
|
uint64_t alignment) const {
|
|
ASSERT(IsPowerOfTwo(alignment));
|
|
// The current level is the level that corresponds to the allocation size. The free list may
|
|
// not contain a block at that level until a larger one gets allocated (and splits).
|
|
// Continue to go up the tree until such a larger block exists.
|
|
//
|
|
// Even if the block exists at the level, it cannot be used if it's offset is unaligned.
|
|
// When the alignment is also a power-of-two, we simply use the next free block whose size
|
|
// is greater than or equal to the alignment value.
|
|
//
|
|
// After one 8-byte allocation:
|
|
//
|
|
// Level --------------------------------
|
|
// 0 32 | S |
|
|
// --------------------------------
|
|
// 1 16 | S | F2 | S - split
|
|
// -------------------------------- F - free
|
|
// 2 8 | Aa | F1 | | A - allocated
|
|
// --------------------------------
|
|
//
|
|
// Allocate(size=8, alignment=8) will be satisfied by using F1.
|
|
// Allocate(size=8, alignment=4) will be satified by using F1.
|
|
// Allocate(size=8, alignment=16) will be satisified by using F2.
|
|
//
|
|
for (size_t ii = 0; ii <= allocationBlockLevel; ++ii) {
|
|
size_t currLevel = allocationBlockLevel - ii;
|
|
BuddyBlock* freeBlock = mFreeLists[currLevel].head;
|
|
if (freeBlock && (freeBlock->mOffset % alignment == 0)) {
|
|
return currLevel;
|
|
}
|
|
}
|
|
return kInvalidOffset; // No free block exists at any level.
|
|
}
|
|
|
|
// Inserts existing free block into the free-list.
|
|
// Called by allocate upon splitting to insert a child block into a free-list.
|
|
// Note: Always insert into the head of the free-list. As when a larger free block at a lower
|
|
// level was split, there were no smaller free blocks at a higher level to allocate.
|
|
void BuddyAllocator::InsertFreeBlock(BuddyBlock* block, size_t level) {
|
|
ASSERT(block->mState == BlockState::Free);
|
|
|
|
// Inserted block is now the front (no prev).
|
|
block->free.pPrev = nullptr;
|
|
|
|
// Old head is now the inserted block's next.
|
|
block->free.pNext = mFreeLists[level].head;
|
|
|
|
// Block already in HEAD position (ex. right child was inserted first).
|
|
if (mFreeLists[level].head != nullptr) {
|
|
// Old head's previous is the inserted block.
|
|
mFreeLists[level].head->free.pPrev = block;
|
|
}
|
|
|
|
mFreeLists[level].head = block;
|
|
}
|
|
|
|
void BuddyAllocator::RemoveFreeBlock(BuddyBlock* block, size_t level) {
|
|
ASSERT(block->mState == BlockState::Free);
|
|
|
|
if (mFreeLists[level].head == block) {
|
|
// Block is in HEAD position.
|
|
mFreeLists[level].head = mFreeLists[level].head->free.pNext;
|
|
} else {
|
|
// Block is after HEAD position.
|
|
BuddyBlock* pPrev = block->free.pPrev;
|
|
BuddyBlock* pNext = block->free.pNext;
|
|
|
|
ASSERT(pPrev != nullptr);
|
|
ASSERT(pPrev->mState == BlockState::Free);
|
|
|
|
pPrev->free.pNext = pNext;
|
|
|
|
if (pNext != nullptr) {
|
|
ASSERT(pNext->mState == BlockState::Free);
|
|
pNext->free.pPrev = pPrev;
|
|
}
|
|
}
|
|
}
|
|
|
|
uint64_t BuddyAllocator::Allocate(uint64_t allocationSize, uint64_t alignment) {
|
|
if (allocationSize == 0 || allocationSize > mMaxBlockSize) {
|
|
return kInvalidOffset;
|
|
}
|
|
|
|
// Compute the level
|
|
const uint32_t allocationSizeToLevel = ComputeLevelFromBlockSize(allocationSize);
|
|
|
|
ASSERT(allocationSizeToLevel < mFreeLists.size());
|
|
|
|
uint64_t currBlockLevel = GetNextFreeAlignedBlock(allocationSizeToLevel, alignment);
|
|
|
|
// Error when no free blocks exist (allocator is full)
|
|
if (currBlockLevel == kInvalidOffset) {
|
|
return kInvalidOffset;
|
|
}
|
|
|
|
// Split free blocks level-by-level.
|
|
// Terminate when the current block level is equal to the computed level of the requested
|
|
// allocation.
|
|
BuddyBlock* currBlock = mFreeLists[currBlockLevel].head;
|
|
|
|
for (; currBlockLevel < allocationSizeToLevel; currBlockLevel++) {
|
|
ASSERT(currBlock->mState == BlockState::Free);
|
|
|
|
// Remove curr block (about to be split).
|
|
RemoveFreeBlock(currBlock, currBlockLevel);
|
|
|
|
// Create two free child blocks (the buddies).
|
|
const uint64_t nextLevelSize = currBlock->mSize / 2;
|
|
BuddyBlock* leftChildBlock = new BuddyBlock(nextLevelSize, currBlock->mOffset);
|
|
BuddyBlock* rightChildBlock =
|
|
new BuddyBlock(nextLevelSize, currBlock->mOffset + nextLevelSize);
|
|
|
|
// Remember the parent to merge these back upon de-allocation.
|
|
rightChildBlock->pParent = currBlock;
|
|
leftChildBlock->pParent = currBlock;
|
|
|
|
// Make them buddies.
|
|
leftChildBlock->pBuddy = rightChildBlock;
|
|
rightChildBlock->pBuddy = leftChildBlock;
|
|
|
|
// Insert the children back into the free list into the next level.
|
|
// The free list does not require a specific order. However, an order is specified as
|
|
// it's ideal to allocate lower addresses first by having the leftmost child in HEAD.
|
|
InsertFreeBlock(rightChildBlock, currBlockLevel + 1);
|
|
InsertFreeBlock(leftChildBlock, currBlockLevel + 1);
|
|
|
|
// Curr block is now split.
|
|
currBlock->mState = BlockState::Split;
|
|
currBlock->split.pLeft = leftChildBlock;
|
|
|
|
// Decend down into the next level.
|
|
currBlock = leftChildBlock;
|
|
}
|
|
|
|
// Remove curr block from free-list (now allocated).
|
|
RemoveFreeBlock(currBlock, currBlockLevel);
|
|
currBlock->mState = BlockState::Allocated;
|
|
|
|
return currBlock->mOffset;
|
|
}
|
|
|
|
void BuddyAllocator::Deallocate(uint64_t offset) {
|
|
BuddyBlock* curr = mRoot;
|
|
|
|
// TODO(bryan.bernhart@intel.com): Optimize de-allocation.
|
|
// Passing allocationSize directly will avoid the following level-by-level search;
|
|
// however, it requires the size information to be stored outside the allocator.
|
|
|
|
// Search for the free block node that corresponds to the block offset.
|
|
size_t currBlockLevel = 0;
|
|
while (curr->mState == BlockState::Split) {
|
|
if (offset < curr->split.pLeft->pBuddy->mOffset) {
|
|
curr = curr->split.pLeft;
|
|
} else {
|
|
curr = curr->split.pLeft->pBuddy;
|
|
}
|
|
|
|
currBlockLevel++;
|
|
}
|
|
|
|
ASSERT(curr->mState == BlockState::Allocated);
|
|
|
|
// Ensure the block is at the correct level
|
|
ASSERT(currBlockLevel == ComputeLevelFromBlockSize(curr->mSize));
|
|
|
|
// Mark curr free so we can merge.
|
|
curr->mState = BlockState::Free;
|
|
|
|
// Merge the buddies (LevelN-to-Level0).
|
|
while (currBlockLevel > 0 && curr->pBuddy->mState == BlockState::Free) {
|
|
// Remove the buddy.
|
|
RemoveFreeBlock(curr->pBuddy, currBlockLevel);
|
|
|
|
BuddyBlock* parent = curr->pParent;
|
|
|
|
// The buddies were inserted in a specific order but
|
|
// could be deleted in any order.
|
|
DeleteBlock(curr->pBuddy);
|
|
DeleteBlock(curr);
|
|
|
|
// Parent is now free.
|
|
parent->mState = BlockState::Free;
|
|
|
|
// Ascend up to the next level (parent block).
|
|
curr = parent;
|
|
currBlockLevel--;
|
|
}
|
|
|
|
InsertFreeBlock(curr, currBlockLevel);
|
|
}
|
|
|
|
// Helper which deletes a block in the tree recursively (post-order).
|
|
void BuddyAllocator::DeleteBlock(BuddyBlock* block) {
|
|
ASSERT(block != nullptr);
|
|
|
|
if (block->mState == BlockState::Split) {
|
|
// Delete the pair in same order we inserted.
|
|
DeleteBlock(block->split.pLeft->pBuddy);
|
|
DeleteBlock(block->split.pLeft);
|
|
}
|
|
delete block;
|
|
}
|
|
|
|
} // namespace dawn_native
|