blob: 29b18d6bca0df367427a4c1f23c78796702cbc93 [file] [log] [blame]
// 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 <lib/inspect/cpp/vmo/heap.h>
#include <zircon/process.h>
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(zx::vmo vmo) : vmo_(std::move(vmo)) {
ZX_ASSERT(ZX_OK == vmo_.get_size(&max_size_));
ZX_ASSERT(max_size_ > 0);
ZX_ASSERT(ZX_OK == zx_vmar_map(zx_vmar_root_self(), ZX_VM_PERM_READ | ZX_VM_PERM_WRITE, 0,
vmo_.get(), 0, max_size_, &buffer_addr_));
Extend(kMinVmoSize);
}
Heap::~Heap() {
zx_vmar_unmap(zx_vmar_root_self(), buffer_addr_, max_size_);
ZX_DEBUG_ASSERT_MSG(num_allocated_blocks_ == 0, "There are still %lu outstanding blocks",
num_allocated_blocks_);
}
const zx::vmo& Heap::GetVmo() const { return vmo_; }
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<size_t>(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<size_t>(cur->header);
if (next == block) {
FreeBlockFields::NextFreeBlock::Set(
&cur->header, FreeBlockFields::NextFreeBlock::Get<size_t>(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 = std::min(max_size_, new_size);
if (cur_size_ >= new_size) {
return ZX_OK;
}
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->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