kiba-engine
free_list.c
Go to the documentation of this file.
1 
7 
8 #include <kiba/core/log.h>
9 #include <kiba/core/memory.h>
10 
11 #define KB_FL_ALLOC_CONSISTENCY_CHECK 0
12 
13 /* Free list */
14 
21 typedef struct free_list_entry {
23  usize size;
27 
35 void free_list_entry_create(free_list_entry *entry, usize data_size);
51 b8 free_list_insert(free_list_entry **head, usize size, void *at);
73 b8 free_list_remove(free_list_entry **head, usize size, void **res);
74 
87 #define PTR_COMPARE(ptr1, offset1, op, ptr2, offset2) ((((u8 *) (ptr1)) + (offset1)) op(((u8 *) (ptr2)) + (offset2)))
88 
89 void free_list_entry_create(free_list_entry *entry, usize data_size) {
90  entry->size = data_size;
91  entry->next = KB_NULL;
92  KB_TRACE("created free list with data size {usize} for head {pointer}", data_size, (void *) entry);
93 }
94 
95 b8 free_list_insert(free_list_entry **head, usize size, void *at) {
96  free_list_entry *entry = *head;
97  free_list_entry *previous = KB_NULL;
98  free_list_entry *at_entry = at;
99  --at_entry; // recover free_list_entry
100  KB_TRACE("trying to add entry with size {usize} at {pointer} to free list for head {pointer}",
101  size,
102  at,
103  (void *) head);
104  if (entry == KB_NULL) {
105  // list is empty -> create new one
106  free_list_entry_create(at_entry, size);
107  *head = at_entry;
108  return true;
109  }
110  while (entry != KB_NULL) {
111  if (PTR_COMPARE(entry + 1, entry->size, ==, at_entry, 0)) {
112  // merge with previous
113  KB_TRACE("adding new entry by merging with previous entry");
114  entry->size += size + sizeof(free_list_entry);
115  // optionally merge with next before returning
116  if (entry->next && PTR_COMPARE(entry->next, 0, ==, at, size)) {
117  KB_TRACE("additionally merging with next entry");
118  entry->size += entry->next->size + sizeof(free_list_entry);
119  entry->next = entry->next->next;
120  }
121  return true;
122  }
123  if (PTR_COMPARE(entry + 1, entry->size, >, at_entry, 0)) {
124  if (PTR_COMPARE(entry, 0, ==, at, size)) {
125  // merge with next
126  KB_TRACE("adding new entry by merging with next entry");
127  at_entry->next = entry->next;
128  at_entry->size = size + entry->size + sizeof(free_list_entry);
129  } else {
130  // needs to create new entry
131  KB_TRACE("try creating new entry");
132  at_entry->size = size;
133  at_entry->next = entry;
134  }
135  if (previous != KB_NULL) {
136  KB_TRACE("adding new entry as part of the existing list");
137  previous->next = at_entry;
138  } else {
139  KB_TRACE("adding new entry as new head of the list");
140  *head = at_entry;
141  }
142  return true;
143  }
144  previous = entry;
145  entry = entry->next;
146  }
147  // reached end of the list
148  KB_TRACE("adding new entry at the end of the list");
149  at_entry->size = size;
150  at_entry->next = KB_NULL;
151  previous->next = at_entry;
152  return true;
153 }
154 
155 b8 free_list_remove(free_list_entry **head, usize size, void **res) {
156  free_list_entry *entry = *head;
157  free_list_entry *previous = KB_NULL;
158  KB_TRACE("trying to remove entry with size {usize} from free list for head {pointer}", size, (void *) head);
159  while (entry != KB_NULL) {
160  if (entry->size == size) {
161  // skip entry of size 0, keep it for free operation though
162  if (previous != KB_NULL) {
163  previous->next = entry->next;
164  } else {
165  *head = entry->next;
166  }
167  *res = entry + 1;
168  return true;
169  } else if (entry->size > size + sizeof(free_list_entry)) {
170  u8 *data_ptr = (void *) (entry + 1);
171  data_ptr += entry->size - size; // use memory at the end of the free space to keep the entry stable
172  *res = data_ptr;
173  entry->size -= size + sizeof(free_list_entry);
174  return true;
175  }
176  previous = entry;
177  entry = entry->next;
178  }
179  KB_TRACE("did not find fitting entry in free list");
180  return false;
181 }
182 
183 static void free_list_check_consistency(const char *prefix, struct free_list_entry *head) {
184 #if KB_FL_ALLOC_CONSISTENCY_CHECK == 1
185  KB_DEBUG("checking free list {pointer}", head);
186  struct free_list_entry *entry = head;
187  struct free_list_entry *previous = KB_NULL;
188  while (entry) {
189  KB_DEBUG("{16l:raw_string} entry at {10r:u64} with size {10r:usize} (entry:{pointer}, data:{pointer})",
190  prefix,
191  ((u8 *) entry) - ((u8 *) head),
192  entry->size,
193  entry,
194  entry + 1);
195  if (previous && PTR_COMPARE(previous + 1, previous->size, >=, entry, 0)) {
196  KB_ASSERT(false, "inconsistent free list entry, size overlaps next entry");
197  }
198  previous = entry;
199  entry = entry->next;
200  }
201 #else
202  UNUSED(prefix);
203  UNUSED(head);
204 #endif
205 }
206 
207 #undef PTR_COMPARE
208 
209 /* Free list allocator */
210 
221  void *data;
223  usize size;
229 
248 
250  usize block_size = 0;
251  usize header_offset = sizeof(free_list_allocator_block) + sizeof(free_list_entry);
252  free_list_allocator_block *block = memory_page_allocate(header_offset + size, &block_size);
253  if (block == KB_NULL) {
254  KB_ERROR("could not allocate memory for free_list_allocator_block");
255  return KB_NULL;
256  }
257  block_size -= header_offset;
258  block->data = block + 1;
259  block->size = block_size;
260  block->next = next;
261  block->free_list = block->data;
262  free_list_entry_create(block->free_list, block_size);
263  return block;
264 }
265 
266 void free_list_allocator_block_destroy(free_list_allocator_block *block) { memory_page_free(block); }
267 
270  if (block == KB_NULL) {
271  KB_ERROR("could not allocate memory for initial free_list_allocator_block");
272  return false;
273  }
279  alloc->state = block;
280  KB_TRACE("created free_list_allocator with initial size {usize}", size);
281  return true;
282 }
283 
285  free_list_allocator_block *block = alloc->state;
286  free_list_allocator_block *prev_block = KB_NULL;
287  while (block != KB_NULL) {
288  KB_TRACE("destroying free_list_allocator_block {pointer} and free_list {pointer}",
289  block,
290  (void *) block->free_list);
291  prev_block = block;
292  block = block->next;
294  }
295  KB_TRACE("destroyed free_list_allocator");
296 }
297 
299  free_list_allocator_block *block = alloc->state;
300  free_list_allocator_block *prev_block = KB_NULL;
301  void *res = KB_NULL;
302  while (block != KB_NULL) {
303  free_list_check_consistency("pre-alloc", block->free_list);
304  if (free_list_remove(&block->free_list, size, &res)) {
305  free_list_check_consistency("post-alloc-succ", block->free_list);
306  break;
307  }
308  prev_block = block;
309  block = block->next;
310  }
311  if (res == KB_NULL) {
312  KB_TRACE("did not find fitting block, creating new one");
313  usize new_size = prev_block->size;
314  if (size > new_size) {
315  KB_TRACE(
316  "new block size {usize} is not enough for requested allocation of {usize} bytes, using the requested "
317  "size as new block size",
318  new_size,
319  size);
320  new_size = size;
321  }
323  if (fitting_block == KB_NULL) {
324  KB_ERROR("could not allocate memory for free_list_allocator_block");
325  return KB_NULL;
326  }
327  prev_block->next = fitting_block;
328  KB_TRACE("created new block to hold {usize} bytes", new_size);
329  KB_ASSERT(free_list_remove(&fitting_block->free_list, size, &res),
330  "a created block must be able to find space in its free list");
331  }
332 
333  return res;
334 }
335 
336 void free_list_allocator_free(allocator *alloc, void *mem, usize size) {
337  free_list_allocator_block *block = alloc->state;
338  while (block != KB_NULL) {
339  u8 *mem_tmp = mem;
340  if (mem_tmp > (u8 *) block->data && mem_tmp < (u8 *) block->data + block->size) {
341  free_list_check_consistency("pre-free", block->free_list);
342  if (!free_list_insert(&block->free_list, size, mem)) {
343  KB_WARN("free_list_allocator was not able to track freed memory, leaking memory until destruction");
344  }
345  free_list_check_consistency("post-free-succ", block->free_list);
346  break;
347  }
348  block = block->next;
349  }
350 }
351 
353  free_list_allocator_block *block = alloc->state;
354  while (block != KB_NULL) {
355  free_list_entry_create(block->free_list, block->size);
356  block = block->next;
357  }
358  KB_TRACE("reset free_list_allocator");
359 }
void allocator_implementation(allocator *alloc, allocator_impl_destroy_fn destroy, allocator_impl_allocate_fn allocate, allocator_impl_free_fn free, allocator_impl_free_all_fn free_all)
Set allocator implementation.
Definition: allocator.c:149
Lightweight layer between platform and other engine components to enable tracing/monitoring.
#define UNUSED(x)
Mark parameter as unused.
Definition: defines.h:21
#define KB_NULL
Value of an invalid ptr (nullptr).
Definition: defines.h:18
void free_list_entry_create(free_list_entry *entry, usize data_size)
Initialize a free_list_list_entry for a block of a certain size.
Definition: free_list.c:89
b8 free_list_allocator_create(allocator *alloc, usize size)
Definition: free_list.c:268
#define PTR_COMPARE(ptr1, offset1, op, ptr2, offset2)
Compare two pointers with byte-wise offsets applied.
Definition: free_list.c:87
void free_list_allocator_free(allocator *alloc, void *mem, usize size)
Definition: free_list.c:336
b8 free_list_remove(free_list_entry **head, usize size, void **res)
Find a suitable offset for a requested size in the free list.
Definition: free_list.c:155
void free_list_allocator_block_destroy(free_list_allocator_block *block)
Destroy a free_list_allocator_block.
Definition: free_list.c:266
void * free_list_allocator_allocate(allocator *alloc, usize size)
Definition: free_list.c:298
b8 free_list_insert(free_list_entry **head, usize size, void *at)
Add a free_list_entry to the free list.
Definition: free_list.c:95
void free_list_allocator_free_all(allocator *alloc)
Definition: free_list.c:352
free_list_allocator_block * free_list_allocator_block_create(usize size, free_list_allocator_block *next)
Create a new free_list_allocator_block.
Definition: free_list.c:249
struct free_list_entry free_list_entry
A single entry of a free list.
void free_list_allocator_destroy(allocator *alloc)
Definition: free_list.c:284
struct free_list_allocator_block free_list_allocator_block
Internal state of the free_list_allocator.
Function signatures for free list based allocator implementation.
Logging system.
#define KB_DEBUG(...)
Log entry with debug log level.
Definition: log.h:163
#define KB_ASSERT(expr,...)
Perform runtime assertion and log failures.
Definition: log.h:133
#define KB_WARN(...)
Log entry with warn log level.
Definition: log.h:161
#define KB_ERROR(...)
Log entry with error log level.
Definition: log.h:142
#define KB_TRACE(...)
Log entry with trace log level.
Definition: log.h:164
Central allocator structure.
Definition: allocator.h:87
void * state
Type specific state of the allocator.
Definition: allocator.h:91
Internal state of the free_list_allocator.
Definition: free_list.c:219
usize size
Size in bytes of the usable data block.
Definition: free_list.c:223
free_list_entry * free_list
Pointer to free list.
Definition: free_list.c:227
void * data
Pointer to beginning of managed block.
Definition: free_list.c:221
struct free_list_allocator_block * next
Pointer to the next block in the linked list.
Definition: free_list.c:225
A single entry of a free list.
Definition: free_list.c:21
struct free_list_entry * next
Pointer to the next entry in the free list.
Definition: free_list.c:25
usize size
The size in bytes of the free space in the associsated memory block.
Definition: free_list.c:23