// 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 currLevel = allocationBlockLevel; currLevel >= 0; currLevel--) { BuddyBlock* freeBlock = mFreeLists[currLevel].head; if (freeBlock && (freeBlock->mOffset % alignment == 0)) { return currLevel; } if (currLevel == 0) { break; } } 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