// Copyright 2018 The Fuchsia Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include namespace inspect { namespace internal { namespace { // Get the "buddy" for a given |Block|. Buddies may be merged together if they are both free. constexpr BlockIndex Buddy(BlockIndex block, BlockOrder block_order) { // XOR index of the block by its size (in kMinOrderSize blocks) to get the index of its // buddy. return block ^ IndexForOffset(OrderToSize(block_order)); } } // namespace Heap::Heap(fbl::unique_ptr vmo, size_t max_size) : vmo_(std::move(vmo)), cur_size_(0), max_size_(max_size) { ZX_DEBUG_ASSERT_MSG(max_size % kMinVmoSize == 0, "Maximum size must be a multiple of the page size."); Extend(kMinVmoSize); } Heap::~Heap() { ZX_DEBUG_ASSERT_MSG(num_allocated_blocks_ == 0, "There are still %lu outstanding blocks", num_allocated_blocks_); } zx::vmo Heap::ReadOnlyClone() const { zx::vmo ret; // BASIC rights include the ability to duplicate, transfer, and wait // on the handle vmo_->vmo().duplicate(ZX_RIGHTS_BASIC | ZX_RIGHT_MAP | ZX_RIGHT_READ, &ret); return ret; } zx_status_t Heap::Allocate(size_t min_size, BlockIndex* out_block) { ZX_DEBUG_ASSERT_MSG(min_size >= sizeof(Block), "Block allocation size %lu is too small", min_size); const BlockOrder min_fit_order = FitOrder(min_size); ZX_DEBUG_ASSERT_MSG(min_fit_order < kNumOrders, "Order %u is greater than maximum order %lu", min_fit_order, kNumOrders - 1); if (min_fit_order >= kNumOrders) { return ZX_ERR_INVALID_ARGS; } // Iterate through the orders until we find a free block with order >= // what is needed. BlockOrder next_order = kNumOrders; for (BlockOrder i = min_fit_order; i < kNumOrders; i++) { if (IsFreeBlock(free_blocks_[i], i)) { next_order = i; break; } } // If no free block is found, extend the VMO and use one of the newly // created free blocks. if (next_order == kNumOrders) { zx_status_t status; status = Extend(cur_size_ * 2); if (status != ZX_OK) { return status; } next_order = kNumOrders - 1; ZX_ASSERT(IsFreeBlock(free_blocks_[kNumOrders - 1], kNumOrders - 1)); } // Once a free block is found, split it repeatedly until it is the // right size. BlockIndex next_block_index = free_blocks_[next_order]; while (GetOrder(GetBlock(next_block_index)) > min_fit_order) { if (!SplitBlock(next_block_index)) { return ZX_ERR_INTERNAL; } } // Remove the block from the free list, clear, and reserve it. RemoveFree(next_block_index); auto* next_block = GetBlock(next_block_index); next_block->header = BlockFields::Order::Make(GetOrder(next_block)) | BlockFields::Type::Make(BlockType::kReserved); *out_block = next_block_index; ++num_allocated_blocks_; return ZX_OK; } void Heap::Free(BlockIndex block_index) { auto* block = GetBlock(block_index); BlockIndex buddy_index = Buddy(block_index, GetOrder(block)); auto* buddy = GetBlock(buddy_index); // Repeatedly merge buddies of the freed block until the buddy is // not free or we hit the maximum block size. while (GetType(buddy) == BlockType::kFree && GetOrder(block) < kNumOrders - 1 && GetOrder(block) == GetOrder(buddy)) { RemoveFree(buddy_index); if (buddy < block) { // We must always merge into the lower index block. // If the buddy of the block is a lower index, we need to swap // index and pointers. std::swap(block, buddy); std::swap(block_index, buddy_index); } BlockFields::Order::Set(&block->header, GetOrder(block) + 1); buddy_index = Buddy(block_index, GetOrder(block)); buddy = GetBlock(buddy_index); } // Complete freeing the block by linking it onto the free list. block->header = BlockFields::Order::Make(GetOrder(block)) | BlockFields::Type::Make(BlockType::kFree) | FreeBlockFields::NextFreeBlock::Make(free_blocks_[GetOrder(block)]); free_blocks_[GetOrder(block)] = block_index; --num_allocated_blocks_; } bool Heap::SplitBlock(BlockIndex block) { RemoveFree(block); auto* cur = GetBlock(block); BlockOrder order = GetOrder(cur); ZX_DEBUG_ASSERT_MSG(order < kNumOrders, "Order on block is invalid"); if (order >= kNumOrders) { return false; } // Lower the order of the original block, then find its new buddy. // Both the original block and the new buddy need to be added // onto the free list of the new order. BlockIndex buddy_index = Buddy(block, order - 1); auto* buddy = GetBlock(buddy_index); cur->header = BlockFields::Order::Make(order - 1) | BlockFields::Type::Make(BlockType::kFree) | FreeBlockFields::NextFreeBlock::Make(buddy_index); buddy->header = BlockFields::Order::Make(order - 1) | BlockFields::Type::Make(BlockType::kFree) | FreeBlockFields::NextFreeBlock::Make(free_blocks_[order - 1]); free_blocks_[order - 1] = block; return true; } bool Heap::RemoveFree(BlockIndex block) { auto* to_remove = GetBlock(block); BlockOrder order = GetOrder(to_remove); ZX_DEBUG_ASSERT_MSG(order < kNumOrders, "Order %u on block %lu is invalid", order, block); if (order >= kNumOrders) { return false; } // If the block we are removing is at the head of the list, // immediately unlink it and return. BlockIndex next = free_blocks_[order]; if (next == block) { free_blocks_[order] = FreeBlockFields::NextFreeBlock::Get(to_remove->header); return true; } // Look through the free list until we find the position for the block, // then unlink it. while (IsFreeBlock(next, order)) { auto* cur = GetBlock(next); next = FreeBlockFields::NextFreeBlock::Get(cur->header); if (next == block) { FreeBlockFields::NextFreeBlock::Set( &cur->header, FreeBlockFields::NextFreeBlock::Get(to_remove->header)); return true; } } return false; } zx_status_t Heap::Extend(size_t new_size) { if (cur_size_ == max_size_ && new_size > max_size_) { return ZX_ERR_NO_MEMORY; } new_size = fbl::min(max_size_, new_size); // Try to grow the VMO. // Note VMO growing will always align to a page boundary (which will // be a multiple of kMaxVmoSize and kMaxOrderSize). if (vmo_->size() < new_size) { zx_status_t status; status = vmo_->Grow(new_size); if (status != ZX_OK) { return status; } } size_t min_index = IndexForOffset(cur_size_); size_t last_index = free_blocks_[kNumOrders - 1]; // Ensure we start on an index at a page boundary. // Convert each new max order block to a free block in descending // order on the free list. size_t cur_index = IndexForOffset(new_size - new_size % kMinVmoSize); do { cur_index -= IndexForOffset(kMaxOrderSize); auto* block = GetBlock(cur_index); *block = {}; block->header = BlockFields::Order::Make(kNumOrders - 1) | BlockFields::Type::Make(BlockType::kFree) | FreeBlockFields::NextFreeBlock::Make(last_index); last_index = cur_index; } while (cur_index > min_index); free_blocks_[kNumOrders - 1] = last_index; cur_size_ = new_size; return ZX_OK; } } // namespace internal } // namespace inspect