Quake II RTX doxygen  1.0 dev
buddy_allocator.c
Go to the documentation of this file.
1 /*
2 Copyright (C) 2019, NVIDIA CORPORATION. All rights reserved.
3 
4 This program is free software; you can redistribute it and/or modify
5 it under the terms of the GNU General Public License as published by
6 the Free Software Foundation; either version 2 of the License, or
7 (at your option) any later version.
8 
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 GNU General Public License for more details.
13 
14 You should have received a copy of the GNU General Public License along
15 with this program; if not, write to the Free Software Foundation, Inc.,
16 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
17 */
18 
19 #include "shared/shared.h"
20 #include "buddy_allocator.h"
21 #include <assert.h>
22 
23 typedef enum AllocatorBlockState
24 {
30 
31 typedef struct AllocatorFreeListItem
32 {
33  uint32_t block_index;
36 
37 typedef struct BuddyAllocator
38 {
39  uint32_t base_level;
40  uint32_t level_num;
42  uint8_t* block_states;
44  void* memory;
46 
47 static inline size_t _align(size_t value, size_t alignment);
48 static inline int32_t uint_log2(uint64_t x);
50 static inline void free_list_item(BuddyAllocator* allocator, AllocatorFreeListItem* item);
51 static inline void write_free_block_to_list(BuddyAllocator* allocator, uint32_t level, uint32_t block_index);
52 static inline uint32_t get_level_offset(BuddyAllocator* allocator, uint32_t level);
53 static inline uint32_t get_next_level_offset(BuddyAllocator* allocator, uint32_t level, uint32_t offset);
54 
55 void subdivide_block(BuddyAllocator* allocator, uint32_t src_level, uint32_t dst_level);
56 qboolean merge_blocks(BuddyAllocator* allocator, uint32_t level, uint32_t block_index);
57 void remove_block_from_free_list(BuddyAllocator* allocator, uint32_t level, uint32_t block_index);
58 
59 BuddyAllocator* create_buddy_allocator(uint64_t capacity, uint64_t block_size)
60 {
61  const uint32_t base_level = uint_log2(block_size);
62  const uint32_t level_num = uint_log2(capacity) - base_level + 1;
63 
64  uint32_t block_num = 0;
65  for (uint32_t i = 0; i < level_num; i++)
66  block_num += 1 << ((level_num - 1) - i);
67 
68  const size_t alignment = 16;
69  const size_t allocator_size = _align(sizeof(BuddyAllocator), alignment);
70  const size_t free_list_array_size = _align(level_num * sizeof(AllocatorFreeListItem*), alignment);
71  const size_t free_item_buffer_size = _align(block_num * sizeof(AllocatorFreeListItem), alignment);
72  const size_t block_state_size = _align(block_num * sizeof(uint8_t), alignment);
73  char* memory = malloc(allocator_size + free_list_array_size + free_item_buffer_size + block_state_size);
74 
75  BuddyAllocator* allocator = (BuddyAllocator*)memory;
76  allocator->base_level = base_level;
77  allocator->level_num = level_num;
78  allocator->memory = memory;
79  allocator->free_block_lists = (AllocatorFreeListItem**)(memory + allocator_size);
80  allocator->free_items = (AllocatorFreeListItem*)(memory + allocator_size + free_list_array_size);
81  allocator->block_states = (uint8_t*)(memory + allocator_size + free_list_array_size + free_item_buffer_size);
82 
83  memset(allocator->free_block_lists, 0, level_num * sizeof(AllocatorFreeListItem*));
84  memset(allocator->free_items, 0, block_num * sizeof(AllocatorFreeListItem));
85  memset(allocator->block_states, 0, block_num * sizeof(uint8_t));
86 
87  for (uint32_t i = 0; i < block_num; i++)
88  allocator->free_items[i].next = allocator->free_items + i + 1;
89  allocator->free_items[block_num - 1].next = NULL;
90 
91  allocator->block_states[block_num - 1] = BLOCK_FREE;
92  write_free_block_to_list(allocator, level_num - 1, 0);
93 
94  return allocator;
95 }
96 
97 BAResult buddy_allocator_allocate(BuddyAllocator* allocator, uint64_t size, uint64_t alignment, uint64_t* offset)
98 {
99  uint32_t level = max((uint32_t)ceil(log2(size)), allocator->base_level) - allocator->base_level;
100 
101  // The requested size exceeds the allocator capacity
102  if (level >= allocator->level_num)
103  return BA_NOT_ENOUGH_MEMORY;
104 
105  // Every block is aligned to its size
106  const uint64_t block_size = (uint64_t)(1 << (level + allocator->base_level));
107  const uint64_t alignment_size = block_size % alignment;
108 
109  assert(alignment_size == 0);
110  if (alignment_size != 0)
111  return BA_INVALID_ALIGNMENT;
112 
113  uint32_t i = level;
114  for (; i < allocator->level_num && allocator->free_block_lists[i] == NULL; i++)
115  {
116  }
117 
118  if (i == allocator->level_num)
119  return BA_NOT_ENOUGH_MEMORY;
120 
121  if (i > level)
122  subdivide_block(allocator, i, level);
123 
124  AllocatorFreeListItem* item = allocator->free_block_lists[level];
125  allocator->free_block_lists[level] = item->next;
126 
127  const uint32_t level_block_offset = get_level_offset(allocator, level);
128  allocator->block_states[level_block_offset + item->block_index] = BLOCK_ALLOCATED;
129 
130  *offset = item->block_index * block_size;
131  free_list_item(allocator, item);
132 
133  return BA_SUCCESS;
134 }
135 
136 void buddy_allocator_free(BuddyAllocator* allocator, uint64_t offset, uint64_t size)
137 {
138  const uint32_t level = max((uint32_t)ceil(log2(size)), allocator->base_level) - allocator->base_level;
139  const uint64_t block_size = (uint64_t)(1 << (level + allocator->base_level));
140  const uint32_t block_index = offset / block_size;
141 
142  const uint32_t level_block_offset = get_level_offset(allocator, level);
143  assert(allocator->block_states[level_block_offset + block_index] == BLOCK_ALLOCATED);
144  allocator->block_states[level_block_offset + block_index] = BLOCK_FREE;
145 
146  if (!merge_blocks(allocator, level, block_index))
148 }
149 
151 {
152  free(allocator->memory);
153 }
154 
155 void subdivide_block(BuddyAllocator* allocator, uint32_t src_level, uint32_t dst_level)
156 {
157  assert(dst_level < src_level);
158 
159  {
160  // Task free block from the list
161  AllocatorFreeListItem* item = allocator->free_block_lists[src_level];
162  allocator->free_block_lists[src_level] = item->next;
163 
164  // Find offsets for states
165  const uint32_t previous_level_block_offset = get_level_offset(allocator, src_level - 1);
166  const uint32_t current_level_block_offset = get_next_level_offset(allocator, src_level - 1, previous_level_block_offset);
167 
168  const uint32_t block_index = item->block_index;
169 
170  // Change state of the blocks
171  assert(allocator->block_states[current_level_block_offset + block_index] == BLOCK_FREE);
172  allocator->block_states[current_level_block_offset + block_index] = BLOCK_SPLIT;
173  allocator->block_states[previous_level_block_offset + block_index * 2] = BLOCK_FREE;
174  allocator->block_states[previous_level_block_offset + block_index * 2 + 1] = BLOCK_FREE;
175 
176  // Add blocks to free list
177  AllocatorFreeListItem* item0 = allocate_list_item(allocator);
178  AllocatorFreeListItem* item1 = allocate_list_item(allocator);
179  item0->block_index = block_index * 2;
180  item0->next = item1;
181  item1->block_index = block_index * 2 + 1;
182  item1->next = allocator->free_block_lists[src_level - 1];
183  allocator->free_block_lists[src_level - 1] = item0;
184  }
185 
186  if (src_level - 1 != dst_level)
187  subdivide_block(allocator, src_level - 1, dst_level);
188 }
189 
190 qboolean merge_blocks(BuddyAllocator* allocator, uint32_t level, uint32_t block_index)
191 {
192  if (level == allocator->level_num - 1)
193  {
195  return qtrue;
196  }
197 
198  const uint32_t level_block_offset = get_level_offset(allocator, level);
199  const uint32_t buddy_block_index = (block_index % 2) == 0 ? block_index + 1 : block_index - 1;
200 
201  if (allocator->block_states[level_block_offset + buddy_block_index] != BLOCK_FREE)
202  return qfalse;
203 
204  remove_block_from_free_list(allocator, level, buddy_block_index);
205 
206  const uint32_t next_level_block_offset = get_level_offset(allocator, level + 1);
207  const uint32_t next_level_block_index = block_index / 2;
208  assert(allocator->block_states[next_level_block_offset + next_level_block_index] == BLOCK_SPLIT);
209  allocator->block_states[next_level_block_offset + next_level_block_index] = BLOCK_FREE;
210 
211  if (!merge_blocks(allocator, level + 1, next_level_block_index))
212  write_free_block_to_list(allocator, level + 1, next_level_block_index);
213 
214  return qtrue;
215 }
216 
217 void remove_block_from_free_list(BuddyAllocator* allocator, uint32_t level, uint32_t block_index)
218 {
219  AllocatorFreeListItem** prev_next = &allocator->free_block_lists[level];
220  AllocatorFreeListItem* item = allocator->free_block_lists[level];
221 
222  assert(item != NULL);
223  while (item->block_index != block_index)
224  {
225  prev_next = &item->next;
226  item = item->next;
227  assert(item != NULL);
228  }
229 
230  *prev_next = item->next;
231  free_list_item(allocator, item);
232 }
233 
234 static inline size_t _align(size_t value, size_t alignment)
235 {
236  return (value + alignment - 1) / alignment * alignment;
237 }
238 
239 static inline int32_t uint_log2(uint64_t x)
240 {
241  uint32_t result = 0;
242  while (x >>= 1)
243  result++;
244  return result;
245 }
246 
248 {
249  AllocatorFreeListItem* item = allocator->free_items;
250  allocator->free_items = item->next;
251  return item;
252 }
253 
254 static inline void free_list_item(BuddyAllocator* allocator, AllocatorFreeListItem* item)
255 {
256  item->next = allocator->free_items;
257  allocator->free_items = item;
258 }
259 
260 static inline void write_free_block_to_list(BuddyAllocator* allocator, uint32_t level, uint32_t block_index)
261 {
262  AllocatorFreeListItem* item = allocate_list_item(allocator);
263  item->block_index = block_index;
264  item->next = allocator->free_block_lists[level];
265  allocator->free_block_lists[level] = item;
266 }
267 
268 static inline uint32_t get_level_offset(BuddyAllocator* allocator, uint32_t level)
269 {
270  uint32_t offset = 0;
271  for (int32_t i = 0; i < level; i++)
272  offset += 1 << ((allocator->level_num - 1) - i);
273 
274  return offset;
275 }
276 
277 static inline uint32_t get_next_level_offset(BuddyAllocator* allocator, uint32_t prev_level, uint32_t offset)
278 {
279  return offset + (1 << ((allocator->level_num - 1) - prev_level));
280 }
free_list_item
static void free_list_item(BuddyAllocator *allocator, AllocatorFreeListItem *item)
Definition: buddy_allocator.c:254
subdivide_block
void subdivide_block(BuddyAllocator *allocator, uint32_t src_level, uint32_t dst_level)
Definition: buddy_allocator.c:155
BLOCK_SPLIT
@ BLOCK_SPLIT
Definition: buddy_allocator.c:27
buddy_allocator_free
void buddy_allocator_free(BuddyAllocator *allocator, uint64_t offset, uint64_t size)
Definition: buddy_allocator.c:136
AllocatorFreeListItem
struct AllocatorFreeListItem AllocatorFreeListItem
buddy_allocator_allocate
BAResult buddy_allocator_allocate(BuddyAllocator *allocator, uint64_t size, uint64_t alignment, uint64_t *offset)
Definition: buddy_allocator.c:97
uint_log2
static int32_t uint_log2(uint64_t x)
Definition: buddy_allocator.c:239
BuddyAllocator::block_states
uint8_t * block_states
Definition: buddy_allocator.c:42
BA_SUCCESS
@ BA_SUCCESS
Definition: buddy_allocator.h:24
BuddyAllocator::free_block_lists
struct AllocatorFreeListItem ** free_block_lists
Definition: buddy_allocator.c:41
BuddyAllocator::base_level
uint32_t base_level
Definition: buddy_allocator.c:39
BuddyAllocator::free_items
struct AllocatorFreeListItem * free_items
Definition: buddy_allocator.c:43
BLOCK_NONE
@ BLOCK_NONE
Definition: buddy_allocator.c:25
remove_block_from_free_list
void remove_block_from_free_list(BuddyAllocator *allocator, uint32_t level, uint32_t block_index)
Definition: buddy_allocator.c:217
BuddyAllocator
struct BuddyAllocator BuddyAllocator
_align
static size_t _align(size_t value, size_t alignment)
Definition: buddy_allocator.c:234
BLOCK_FREE
@ BLOCK_FREE
Definition: buddy_allocator.c:26
destroy_buddy_allocator
void destroy_buddy_allocator(BuddyAllocator *allocator)
Definition: buddy_allocator.c:150
BuddyAllocator
Definition: buddy_allocator.c:37
BAResult
BAResult
Definition: buddy_allocator.h:22
get_level_offset
static uint32_t get_level_offset(BuddyAllocator *allocator, uint32_t level)
Definition: buddy_allocator.c:268
write_free_block_to_list
static void write_free_block_to_list(BuddyAllocator *allocator, uint32_t level, uint32_t block_index)
Definition: buddy_allocator.c:260
AllocatorFreeListItem::block_index
uint32_t block_index
Definition: buddy_allocator.c:33
BA_INVALID_ALIGNMENT
@ BA_INVALID_ALIGNMENT
Definition: buddy_allocator.h:26
get_next_level_offset
static uint32_t get_next_level_offset(BuddyAllocator *allocator, uint32_t level, uint32_t offset)
Definition: buddy_allocator.c:277
AllocatorBlockState
AllocatorBlockState
Definition: buddy_allocator.c:23
BuddyAllocator::memory
void * memory
Definition: buddy_allocator.c:44
AllocatorFreeListItem::next
struct AllocatorFreeListItem * next
Definition: buddy_allocator.c:34
level
level_locals_t level
Definition: g_main.c:22
buddy_allocator.h
BuddyAllocator::level_num
uint32_t level_num
Definition: buddy_allocator.c:40
BLOCK_ALLOCATED
@ BLOCK_ALLOCATED
Definition: buddy_allocator.c:28
BA_NOT_ENOUGH_MEMORY
@ BA_NOT_ENOUGH_MEMORY
Definition: buddy_allocator.h:25
create_buddy_allocator
BuddyAllocator * create_buddy_allocator(uint64_t capacity, uint64_t block_size)
Definition: buddy_allocator.c:59
merge_blocks
qboolean merge_blocks(BuddyAllocator *allocator, uint32_t level, uint32_t block_index)
Definition: buddy_allocator.c:190
AllocatorFreeListItem
Definition: buddy_allocator.c:31
allocate_list_item
static AllocatorFreeListItem * allocate_list_item(BuddyAllocator *allocator)
Definition: buddy_allocator.c:247