blob: 30edf2419f2c41dbfdef9000dcf24eaaadcf75c2 [file] [log] [blame]
// Copyright 2019 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 <lib/inspect/cpp/vmo/limits.h>
#include <lib/inspect/cpp/vmo/scanner.h>
#include <cstdint>
#include <iostream>
#include <vector>
#include <fbl/string_printf.h>
#include <zxtest/zxtest.h>
namespace {
using inspect::internal::Block;
using inspect::internal::BlockIndex;
using inspect::internal::BlockType;
using inspect::internal::Heap;
using inspect::internal::ScanBlocks;
zx::vmo MakeVmo(size_t size) {
zx::vmo ret;
EXPECT_OK(zx::vmo::create(size, 0, &ret));
return ret;
}
constexpr size_t kMinAllocationSize = sizeof(Block);
struct DebugBlock {
size_t index;
BlockType type;
size_t order;
fbl::String dump() const {
return fbl::StringPrintf("index=%lu type=%d order=%lu", index, static_cast<int>(type), order);
}
bool operator==(const DebugBlock& other) const {
return index == other.index && type == other.type && order == other.order;
}
bool operator!=(const DebugBlock& other) const { return !(*this == other); }
};
std::vector<DebugBlock> dump(const Heap& heap) {
std::vector<DebugBlock> ret;
ZX_ASSERT(ZX_OK ==
ScanBlocks(heap.data(), heap.size(), [&ret](BlockIndex index, const Block* block) {
ret.push_back({index, GetType(block), GetOrder(block)});
return true;
}));
return ret;
}
void ExpectDebugBlock(size_t index, const DebugBlock& expected, const DebugBlock& actual) {
if (expected != actual) {
std::cout << fbl::StringPrintf("Actual: %s, Expected: %s\n", actual.dump().c_str(),
expected.dump().c_str())
.c_str()
<< fbl::StringPrintf("Mismatch at index %lu", index).c_str();
EXPECT_TRUE(false);
}
}
void MatchDebugBlockVectors(const std::vector<DebugBlock>& expected,
const std::vector<DebugBlock>& actual) {
if (expected.size() != actual.size()) {
fprintf(stderr, "Expected:\n");
for (const auto& h : expected) {
fprintf(stderr, " %s\n", h.dump().c_str());
}
fprintf(stderr, "Actual:\n");
for (const auto& h : actual) {
fprintf(stderr, " %s\n", h.dump().c_str());
}
ASSERT_TRUE(false);
}
for (size_t i = 0; i < expected.size(); i++) {
ExpectDebugBlock(i, expected[i], actual[i]);
}
}
TEST(Heap, Create) {
auto vmo = MakeVmo(4096);
ASSERT_TRUE(!!vmo);
Heap heap(std::move(vmo));
EXPECT_FALSE(heap.GetHeaderBlock());
MatchDebugBlockVectors({{0, BlockType::kFree, 7}, {128, BlockType::kFree, 7}}, dump(heap));
}
TEST(Heap, Allocate) {
auto vmo = MakeVmo(4096);
ASSERT_TRUE(!!vmo);
Heap heap(std::move(vmo));
// Allocate a series of small blocks, they should all be in order.
BlockIndex b;
EXPECT_OK(heap.Allocate(kMinAllocationSize, &b));
EXPECT_EQ(0u, b);
EXPECT_OK(heap.Allocate(kMinAllocationSize, &b));
EXPECT_EQ(1u, b);
EXPECT_OK(heap.Allocate(kMinAllocationSize, &b));
EXPECT_EQ(2u, b);
EXPECT_OK(heap.Allocate(kMinAllocationSize, &b));
EXPECT_EQ(3u, b);
EXPECT_OK(heap.Allocate(kMinAllocationSize, &b));
EXPECT_EQ(4u, b);
EXPECT_OK(heap.Allocate(kMinAllocationSize, &b));
EXPECT_EQ(5u, b);
// Free blocks, leaving some in the middle to ensure they chain.
heap.Free(2);
heap.Free(4);
heap.Free(0);
// Allocate small blocks again to see that we get the same ones in reverse order.
EXPECT_OK(heap.Allocate(kMinAllocationSize, &b));
EXPECT_EQ(0u, b);
EXPECT_OK(heap.Allocate(kMinAllocationSize, &b));
EXPECT_EQ(4u, b);
EXPECT_OK(heap.Allocate(kMinAllocationSize, &b));
EXPECT_EQ(2u, b);
// Free everything except for the first two.
heap.Free(4);
heap.Free(2);
heap.Free(3);
heap.Free(5);
MatchDebugBlockVectors({{0, BlockType::kReserved, 0},
{1, BlockType::kReserved, 0},
{2, BlockType::kFree, 1},
{4, BlockType::kFree, 2},
{8, BlockType::kFree, 3},
{16, BlockType::kFree, 4},
{32, BlockType::kFree, 5},
{64, BlockType::kFree, 6},
{128, BlockType::kFree, 7}},
dump(heap));
// Leave a small free hole at 0, allocate something large
// and observe it takes the free largest block.
heap.Free(0);
EXPECT_OK(heap.Allocate(2048, &b));
EXPECT_EQ(128u, b);
// Free the last small allocation, the next large allocation
// takes the first half of the buffer.
heap.Free(1);
EXPECT_OK(heap.Allocate(2048, &b));
EXPECT_EQ(0u, b);
MatchDebugBlockVectors({{0, BlockType::kReserved, 7}, {128, BlockType::kReserved, 7}},
dump(heap));
// Allocate twice in the first half, free in reverse order
// to ensure buddy freeing works left to right and right to left.
heap.Free(0);
EXPECT_OK(heap.Allocate(1024, &b));
EXPECT_EQ(0u, b);
EXPECT_OK(heap.Allocate(1024, &b));
EXPECT_EQ(64u, b);
heap.Free(0);
heap.Free(64);
// Ensure the freed blocks all merged into one big block and that we
// can use the whole space at position 0.
EXPECT_OK(heap.Allocate(2048, &b));
EXPECT_EQ(0u, b);
heap.Free(0);
MatchDebugBlockVectors({{0, BlockType::kFree, 7}, {128, BlockType::kReserved, 7}}, dump(heap));
heap.Free(128);
}
TEST(Heap, MergeBlockedByAllocation) {
auto vmo = MakeVmo(4096);
ASSERT_TRUE(!!vmo);
Heap heap(std::move(vmo));
// Allocate 4 small blocks at the beginning of the buffer.
BlockIndex b;
EXPECT_OK(heap.Allocate(kMinAllocationSize, &b));
EXPECT_EQ(0u, b);
EXPECT_OK(heap.Allocate(kMinAllocationSize, &b));
EXPECT_EQ(1u, b);
EXPECT_OK(heap.Allocate(kMinAllocationSize, &b));
EXPECT_EQ(2u, b);
EXPECT_OK(heap.Allocate(kMinAllocationSize, &b));
EXPECT_EQ(3u, b);
// Free position 2 first, then 0 and 1.
// The final free sees a situation like:
// FREE | FREE | FREE | RESERVED
// The first two spaces will get merged into an order 1 blocku, but the
// reserved space will prevent merging into an order 2 block.
heap.Free(2);
heap.Free(0);
heap.Free(1);
MatchDebugBlockVectors({{0, BlockType::kFree, 1},
{2, BlockType::kFree, 0},
{3, BlockType::kReserved, 0},
{4, BlockType::kFree, 2},
{8, BlockType::kFree, 3},
{16, BlockType::kFree, 4},
{32, BlockType::kFree, 5},
{64, BlockType::kFree, 6},
{128, BlockType::kFree, 7}},
dump(heap));
heap.Free(3);
MatchDebugBlockVectors({{0, BlockType::kFree, 7}, {128, BlockType::kFree, 7}}, dump(heap));
}
TEST(Heap, Extend) {
auto vmo = MakeVmo(128 * 1024);
ASSERT_TRUE(!!vmo);
Heap heap(std::move(vmo));
// Allocate many large blocks, so the VMO needs to be extended.
BlockIndex b;
EXPECT_OK(heap.Allocate(2048, &b));
EXPECT_EQ(0u, b);
EXPECT_OK(heap.Allocate(2048, &b));
EXPECT_EQ(128u, b);
EXPECT_OK(heap.Allocate(2048, &b));
EXPECT_EQ(256u, b);
MatchDebugBlockVectors({{0, BlockType::kReserved, 7},
{128, BlockType::kReserved, 7},
{256, BlockType::kReserved, 7},
{384, BlockType::kFree, 7}},
dump(heap));
EXPECT_OK(heap.Allocate(2048, &b));
EXPECT_EQ(384u, b);
EXPECT_OK(heap.Allocate(2048, &b));
EXPECT_EQ(512u, b);
EXPECT_EQ(0u, heap.TotalFailedAllocations());
heap.Free(0);
heap.Free(128);
heap.Free(256);
heap.Free(384);
heap.Free(512);
MatchDebugBlockVectors({{0, BlockType::kFree, 7},
{128, BlockType::kFree, 7},
{256, BlockType::kFree, 7},
{384, BlockType::kFree, 7},
{512, BlockType::kFree, 7},
{640, BlockType::kFree, 7}},
dump(heap));
}
TEST(Heap, ExtendFailure) {
auto vmo = MakeVmo(3 * 4096);
ASSERT_TRUE(!!vmo);
Heap heap(std::move(vmo));
// Allocate many large blocks, so the VMO needs to be extended.
BlockIndex b;
EXPECT_OK(heap.Allocate(2048, &b));
EXPECT_EQ(0u, b);
EXPECT_OK(heap.Allocate(2048, &b));
EXPECT_EQ(128u, b);
EXPECT_OK(heap.Allocate(2048, &b));
EXPECT_EQ(256u, b);
EXPECT_OK(heap.Allocate(2048, &b));
EXPECT_EQ(384u, b);
EXPECT_OK(heap.Allocate(2048, &b));
EXPECT_EQ(512u, b);
EXPECT_OK(heap.Allocate(2048, &b));
EXPECT_EQ(640u, b);
EXPECT_EQ(ZX_ERR_NO_MEMORY, heap.Allocate(2048, &b));
EXPECT_EQ(1u, heap.TotalFailedAllocations());
MatchDebugBlockVectors({{0, BlockType::kReserved, 7},
{128, BlockType::kReserved, 7},
{256, BlockType::kReserved, 7},
{384, BlockType::kReserved, 7},
{512, BlockType::kReserved, 7},
{640, BlockType::kReserved, 7}},
dump(heap));
heap.Free(0);
heap.Free(128);
heap.Free(256);
heap.Free(384);
heap.Free(512);
heap.Free(640);
}
TEST(Heap, VmoGreaterThanMaxSize) {
auto vmo = MakeVmo(inspect::internal::kMaxVmoSize + 2048);
ASSERT_TRUE(!!vmo);
Heap heap(std::move(vmo));
BlockIndex b;
for (uint32_t i = 0; i < (inspect::internal::kMaxVmoSize / inspect::internal::kMaxOrderSize);
i++) {
EXPECT_OK(heap.Allocate(2048, &b));
EXPECT_EQ(i * 128, b);
}
EXPECT_EQ(0u, heap.TotalFailedAllocations());
EXPECT_EQ(ZX_ERR_NO_MEMORY, heap.Allocate(2048, &b));
EXPECT_EQ(1u, heap.TotalFailedAllocations());
for (uint32_t i = 0; i < (inspect::internal::kMaxVmoSize / inspect::internal::kMaxOrderSize);
i++) {
heap.Free(i * 128);
}
}
TEST(Heap, UpdateHeaderSize) {
auto vmo = MakeVmo(3 * 4096);
ASSERT_TRUE(!!vmo);
Heap heap(std::move(vmo));
BlockIndex header;
ASSERT_OK(heap.Allocate(sizeof(Block), &header));
EXPECT_EQ(0u, header);
auto* block = heap.GetBlock(header);
block->header =
inspect::internal::HeaderBlockFields::Type::Make(BlockType::kHeader) |
inspect::internal::HeaderBlockFields::Order::Make(inspect::internal::kVmoHeaderOrder) |
inspect::internal::HeaderBlockFields::Version::Make(inspect::internal::kVersion);
memcpy(&block->header_data[4], inspect::internal::kMagicNumber, 4);
block->payload.u64 = 0;
SetHeaderVmoSize(block, heap.size());
heap.SetHeaderBlock(block);
EXPECT_EQ(GetHeaderVmoSize(block), GetHeaderVmoSize(heap.GetHeaderBlock().value()));
// Allocate many large blocks, so the VMO needs to be extended.
BlockIndex b;
EXPECT_OK(heap.Allocate(2048, &b));
EXPECT_EQ(128u, b);
EXPECT_EQ(heap.size(), GetHeaderVmoSize(heap.GetHeaderBlock().value()));
EXPECT_OK(heap.Allocate(2048, &b));
EXPECT_EQ(256u, b);
EXPECT_EQ(heap.size(), GetHeaderVmoSize(heap.GetHeaderBlock().value()));
EXPECT_OK(heap.Allocate(2048, &b));
EXPECT_EQ(384u, b);
EXPECT_EQ(heap.size(), GetHeaderVmoSize(heap.GetHeaderBlock().value()));
MatchDebugBlockVectors({{0, BlockType::kHeader, 1},
{2, BlockType::kFree, 1},
{4, BlockType::kFree, 2},
{8, BlockType::kFree, 3},
{16, BlockType::kFree, 4},
{32, BlockType::kFree, 5},
{64, BlockType::kFree, 6},
{128, BlockType::kReserved, 7},
{256, BlockType::kReserved, 7},
{384, BlockType::kReserved, 7}},
dump(heap));
EXPECT_OK(heap.Allocate(2048, &b));
EXPECT_EQ(512u, b);
EXPECT_EQ(heap.size(), GetHeaderVmoSize(heap.GetHeaderBlock().value()));
EXPECT_OK(heap.Allocate(2048, &b));
EXPECT_EQ(640u, b);
EXPECT_EQ(heap.size(), GetHeaderVmoSize(heap.GetHeaderBlock().value()));
EXPECT_EQ(0u, heap.TotalFailedAllocations());
EXPECT_NOT_OK(heap.Allocate(2048, &b));
EXPECT_EQ(heap.size(), GetHeaderVmoSize(heap.GetHeaderBlock().value()));
EXPECT_EQ(1u, heap.TotalFailedAllocations());
heap.Free(0);
heap.Free(128);
heap.Free(256);
heap.Free(384);
heap.Free(512);
heap.Free(640);
MatchDebugBlockVectors({{0, BlockType::kFree, 7},
{128, BlockType::kFree, 7},
{256, BlockType::kFree, 7},
{384, BlockType::kFree, 7},
{512, BlockType::kFree, 7},
{640, BlockType::kFree, 7}},
dump(heap));
}
} // namespace