Flow
Documentation for the Flow C++ Library
Loading...
Searching...
No Matches
flow_buddy_memory_resource.h
Go to the documentation of this file.
1#pragma once
3#include <bit>
4#include <cassert>
5#include <cstddef>
6#include <exception>
7#include <algorithm>
8
9namespace flow {
10
14 struct BuddyBlock {
16 };
17
18 static constexpr std::size_t kMaxLevel = 64;
19
20 std::byte* beginBuffer_;
21 std::size_t capacity_;
23
24 std::size_t getLevelSize(std::size_t level) const noexcept {
25 return 1ull << level;
26 }
27
28 BuddyBlock* popFront(std::size_t level) noexcept {
29 assert(freeList_[level].next != nullptr && "pop front from an empty list");
30 BuddyBlock* block = freeList_[level].next;
31 freeList_[level].next = block->next;
32 return block;
33 }
34
35 void pushFront(BuddyBlock* block, std::size_t level) noexcept {
36 assert(level < kMaxLevel && "level is larger than the maximum possible level");
37 block->next = freeList_[level].next;
38 freeList_[level].next = block;
39 }
40
41 bool isEmpty(std::size_t level) const noexcept {
42 return !freeList_[level].next;
43 }
44
45 BuddyBlock* getBuddy(BuddyBlock* block, std::size_t blockSize) const noexcept {
46 std::size_t index = reinterpret_cast<std::byte*>(block) - beginBuffer_;
47 std::size_t buddyIndex = index ^ blockSize;
48 if (buddyIndex >= capacity_) {
49 return nullptr;
50 }
51
52 BuddyBlock* buddyBlock = reinterpret_cast<BuddyBlock*>(beginBuffer_ + buddyIndex);
53 return buddyBlock;
54 }
55
56 bool eraseBlock(BuddyBlock* block, std::size_t level) noexcept {
57 BuddyBlock* curr = &freeList_[level];
58 while (curr->next) {
59 BuddyBlock* child = curr->next;
60 if (child == block) {
61 curr->next = child->next;
62 return true;
63 }
64 curr = child;
65 }
66 return false;
67 }
68
69 public:
70
76 explicit BuddyMemoryResource(void* buffer, std::size_t capacity, std::size_t alignment = alignof(std::max_align_t))
77 : freeList_() {
78
79 // Not enough capacity after alignment.
80 if (!std::align(alignment, alignment, buffer, capacity)) {
81 throw std::bad_alloc();
82 }
83 beginBuffer_ = static_cast<std::byte*>(buffer);
84 capacity_ = std::bit_floor(capacity);
85
86 assert(capacity_ > 0 && "buffer capacity must be non-zero after alignment");
87 std::size_t level = std::countr_zero(capacity_);
88 pushFront(reinterpret_cast<BuddyBlock*>(beginBuffer_), level);
89 }
90
91 virtual void* allocateImp(std::size_t bytes, std::size_t alignment) override {
92 std::size_t requiredLevel = std::countr_zero(std::bit_ceil(std::max(bytes, alignment)));
93
94 // Find the smallest available block.
95 std::size_t level = requiredLevel;
96 while (level < kMaxLevel && isEmpty(level)) {
97 ++level;
98 }
99
100 // All blocks are not big enough.
101 if (level >= kMaxLevel) {
102 throw std::bad_alloc();
103 }
104
105 BuddyBlock* block = popFront(level);
106 while (level > requiredLevel) {
107 --level;
108 const std::size_t levelSize = getLevelSize(level);
109
110 BuddyBlock* secondHalf = reinterpret_cast<BuddyBlock*>(reinterpret_cast<std::byte*>(block) + levelSize);
111 pushFront(secondHalf, level);
112 }
113
114 return block;
115 }
116
117 virtual void deallocateImp(void* address, std::size_t bytes, std::size_t alignment) override {
118 if (!address) {
119 return;
120 }
121
122 std::size_t level = std::countr_zero(std::bit_ceil(std::max(bytes, alignment)));
123 for (; level < kMaxLevel; ++level) {
124 BuddyBlock* buddy = getBuddy(static_cast<BuddyBlock*>(address), getLevelSize(level));
125 if (buddy && eraseBlock(buddy, level)) {
126 // Coalesce two blocks.
127 address = std::min(address, static_cast<void*>(buddy));
128 } else {
129 // Push it to the free list.
130 pushFront(static_cast<BuddyBlock*>(address), level);
131 break;
132 }
133 }
134 }
135 };
136}
virtual void * allocateImp(std::size_t bytes, std::size_t alignment) override
bool eraseBlock(BuddyBlock *block, std::size_t level) noexcept
bool isEmpty(std::size_t level) const noexcept
BuddyBlock * popFront(std::size_t level) noexcept
BuddyBlock * getBuddy(BuddyBlock *block, std::size_t blockSize) const noexcept
static constexpr std::size_t kMaxLevel
virtual void deallocateImp(void *address, std::size_t bytes, std::size_t alignment) override
void pushFront(BuddyBlock *block, std::size_t level) noexcept
BuddyMemoryResource(void *buffer, std::size_t capacity, std::size_t alignment=alignof(std::max_align_t))
Constructs a buddy allocator over a user-provided buffer.
std::size_t getLevelSize(std::size_t level) const noexcept
A memory resource holder interface for the PolymorphicAllocator. Responsible for allocate and dealloc...