data-structures-c
Loading...
Searching...
No Matches
freelist.h
Go to the documentation of this file.
1/* arena.h
2 *
3 * Copyright (C) 2023 abxh
4 *
5 * This library is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU Lesser General Public
7 * License as published by the Free Software Foundation; either
8 * version 2.1 of the License, or (at your option) any later version.
9 * See the file LICENSE included with this distribution for more
10 * information. */
11
24#pragma once
25
26#include <stdalign.h>
27#include <stdbool.h>
28#include <stddef.h>
29#include <stdint.h>
30#include <string.h>
31
32#include "align.h" // align, calc_alignment_padding, CALC_ALIGNMENT_PADDING
33
41 char : CALC_ALIGNMENT_PADDING(alignof(max_align_t), 2 * sizeof(size_t));
42};
43
45#define NAME freetree
46#define KEY_TYPE struct freelist_header
47#define KEY_IS_STRICTLY_LESS(a, b) ((a).curr_block_size < (b).curr_block_size)
48#define KEY_MEMBER_IS_FIRST
49#define ALLOW_DUPLICATES
51#include "rbtree.h"
52
56struct freelist {
57 size_t buf_len;
58 size_t buf_used;
60 struct freetree_node *rb_rootptr;
61 unsigned char *buf_ptr;
62};
63
65static inline struct freetree_node *internal_freetree_search_best_block(struct freetree_node **rootptr_ptr,
66 const size_t block_size);
67
68static inline void internal_freelist_coalescence(struct freelist *self, struct freelist_header *header);
70
71/* Create a freelist header from given params */
72static inline struct freelist_header freelist_header_from(const bool is_freed, const size_t prev_size,
73 const size_t curr_size)
74{
75 return (struct freelist_header){.__prev_block_size_w_freed_bit = prev_size + (is_freed ? 1 : 0),
76 .curr_block_size = curr_size};
77}
78
79/* Get the prev size from header */
80static inline size_t freelist_header_prev_size(const struct freelist_header *header)
81{
82 return (header->__prev_block_size_w_freed_bit & ~(size_t)1);
83}
84
85/* Check if the memory block under header is freed */
86static inline bool freelist_header_is_freed(const struct freelist_header *header)
87{
88 return header->__prev_block_size_w_freed_bit & 1;
89}
90
91/* Get the pointer to previous header */
92static inline struct freelist_header *freelist_header_prev(struct freelist_header *header)
93{
94 const size_t prev_size = freelist_header_prev_size(header);
95
96 return prev_size == 0 ? NULL : (struct freelist_header *)((char *)header - prev_size);
97}
98
99/* Get the pointer to next header */
100static inline struct freelist_header *freelist_header_next(struct freelist_header *header,
101 const struct freelist *freelist_ptr)
102{
103 const char *next = (char *)header + header->curr_block_size;
104 const bool is_end = next >= (char *)freelist_ptr->buf_ptr + freelist_ptr->buf_len;
105
106 return is_end ? NULL : (struct freelist_header *)next;
107}
108
109/* Check if the freelist header *should* be in the freetree */
110static inline bool freelist_header_should_be_in_freetree(const struct freelist_header *header)
111{
112 return freelist_header_is_freed(header) && header->curr_block_size >= sizeof(struct freetree_node);
113}
114
115/* Deallocate everything contained in the freelist */
116static inline void freelist_deallocate_all(struct freelist *self)
117{
118 assert(self);
119
120 struct freetree_node *node = (struct freetree_node *)self->buf_ptr;
121
122 freetree_node_init(node, freelist_header_from(true, 0, self->buf_len));
123
124 self->buf_used = self->buf_len - node->key.curr_block_size;
125 self->rb_rootptr = node;
126 self->head = &node->key;
127}
128
129/* Initialize the freelist */
130static inline void freelist_init(struct freelist *self, const size_t len, unsigned char *backing_buf)
131{
132 assert(self);
133 assert(backing_buf);
134
135 const uintptr_t padding = calc_alignment_padding(alignof(struct freelist_header), (uintptr_t)&backing_buf[0]);
136
137 assert(len >= sizeof(struct freetree_node) + padding);
138
139 self->buf_ptr = &backing_buf[padding];
140 self->buf_len = len - padding;
141
142 freelist_deallocate_all(self);
143}
144
146static inline void *internal_freelist_init_block(struct freelist *self, char *block_ptr, struct freelist_header *next,
147 const size_t prev_size, const size_t block_size,
148 const size_t remaining_size)
149{
150 if (remaining_size > 0) {
151 struct freelist_header *remaining_header = (struct freelist_header *)(block_ptr + block_size);
152 *remaining_header = freelist_header_from(true, block_size, remaining_size);
153
154 if (next) {
155 *next = freelist_header_from(freelist_header_is_freed(next), remaining_size, next->curr_block_size);
156 }
157
158 if (freelist_header_should_be_in_freetree(remaining_header)) {
159 struct freetree_node *new_node = (struct freetree_node *)remaining_header;
160 freetree_node_init(new_node, *remaining_header);
161 freetree_insert_node(&self->rb_rootptr, new_node);
162 }
163 }
164 struct freelist_header *curr_header = (struct freelist_header *)block_ptr;
165 *curr_header = freelist_header_from(false, prev_size, block_size);
166
167 return (void *)((char *)curr_header + sizeof(struct freelist_header));
168}
169
170static inline size_t internal_freelist_calc_block_size(size_t requested_size)
171{
172 size_t block_size = sizeof(struct freelist_header) + requested_size;
173 block_size = block_size >= sizeof(struct freetree_node) ? block_size : sizeof(struct freetree_node);
174 block_size = block_size + calc_alignment_padding(alignof(struct freetree_node), block_size);
175
176 return block_size;
177}
179
180/* Get the pointer to a block of the freelist.*/
181static inline void *freelist_allocate(struct freelist *self, const size_t requested_size)
182{
183 assert(self);
184 assert(requested_size != 0);
185
186 const size_t block_size = internal_freelist_calc_block_size(requested_size);
187
188 struct freetree_node *node = internal_freetree_search_best_block(&self->rb_rootptr, block_size);
189
190 if (node == NULL) {
191 return NULL;
192 }
193
194 freetree_delete_node(&self->rb_rootptr, node);
195
196 self->buf_used += block_size;
197
198 return internal_freelist_init_block(self, (char *)node, freelist_header_next(&node->key, self),
199 freelist_header_prev_size(&node->key), block_size,
200 node->key.curr_block_size - block_size);
201}
202
203/* Deallocate a block from the freelist for further use. */
204static inline void freelist_deallocate(struct freelist *self, void *ptr)
205{
206 assert(self->buf_ptr <= (unsigned char *)ptr && (unsigned char *)ptr < &self->buf_ptr[self->buf_len]);
207
208 struct freelist_header *header = (struct freelist_header *)((char *)ptr - sizeof(struct freelist_header));
209
210 assert(!freelist_header_is_freed(header) && "double free detected!");
211
212 self->buf_used -= header->curr_block_size;
213
214 internal_freelist_coalescence(self, header);
215}
216
217/* Reallocate a block and grow / shrink the block. */
218static inline void *freelist_reallocate(struct freelist *self, void *ptr, const size_t new_size)
219{
220 assert(self);
221 assert(new_size != 0);
222 assert(&self->buf_ptr[0] <= (unsigned char *)ptr && (unsigned char *)ptr < &self->buf_ptr[self->buf_len]);
223
224 struct freelist_header *header = (struct freelist_header *)((char *)ptr - sizeof(struct freelist_header));
225 const size_t prev_size = freelist_header_prev_size(header);
226 const size_t old_size = header->curr_block_size - sizeof(struct freelist_header);
227
228 assert(freelist_header_is_freed(header) && "reallocating freed block!");
229
230 if (new_size <= old_size) {
231 const size_t block_size = internal_freelist_calc_block_size(new_size);
232
233 if (block_size == header->curr_block_size) {
234 return ptr;
235 }
236 return internal_freelist_init_block(self, (char *)header, freelist_header_next(header, self), prev_size,
237 block_size, header->curr_block_size - block_size);
238 }
239
240 size_t bytes_acc = header->curr_block_size;
241 struct freelist_header *next = freelist_header_next(header, self);
242 while (bytes_acc < new_size && next != NULL && !freelist_header_is_freed(next)) {
243 bytes_acc += next->curr_block_size;
244 next = freelist_header_next(next, self);
245 }
246
247 if (bytes_acc >= new_size) {
248 return internal_freelist_init_block(self, (char *)header, next, prev_size, new_size, bytes_acc - new_size);
249 }
250
251 void *ptr_new = freelist_allocate(self, new_size);
252 if (!ptr_new) {
253 return NULL;
254 }
255
256 memcpy(ptr_new, ptr, old_size);
257
258 freelist_deallocate(self, ptr);
259
260 return ptr_new;
261}
262
264static inline struct freetree_node *internal_freetree_search_best_block(struct freetree_node **rootptr_ptr,
265 const size_t block_size)
266{
267 assert(rootptr_ptr != NULL);
268
269 struct freetree_node *prev_ptr = NULL;
270 struct freetree_node *curr_ptr = *rootptr_ptr;
271
272 while (curr_ptr != NULL) {
273 if (block_size > curr_ptr->key.curr_block_size) {
274 curr_ptr = curr_ptr->right_ptr;
275 }
276 else {
277 prev_ptr = curr_ptr;
278 curr_ptr = curr_ptr->left_ptr;
279 }
280 }
281 return prev_ptr;
282}
283
284static inline void internal_freelist_coalescence(struct freelist *self, struct freelist_header *header)
285{
286 assert(self);
287 assert(header);
288
289 struct freelist_header *prev = freelist_header_prev(header);
290 struct freelist_header *next = freelist_header_next(header, self);
291
292 struct freelist_header header_new = *header;
293 struct freelist_header *header_addr = header;
294 struct freelist_header *header_next = next;
295
296 if (prev && freelist_header_is_freed(prev)) {
297 header_new.curr_block_size += prev->curr_block_size;
298 header_new.__prev_block_size_w_freed_bit = prev->__prev_block_size_w_freed_bit & ~(uintptr_t)1;
299 header_addr = prev;
300
301 if (freelist_header_should_be_in_freetree(prev)) {
302 freetree_delete_node(&self->rb_rootptr, (struct freetree_node *)prev);
303 }
304 }
305 if (next && freelist_header_is_freed(next)) {
306 header_new.curr_block_size += next->curr_block_size;
307 header_next = freelist_header_next(next, self);
308
309 if (freelist_header_should_be_in_freetree(next)) {
310 freetree_delete_node(&self->rb_rootptr, (struct freetree_node *)next);
311 }
312 }
313 if (header_next) {
314 *header_next = freelist_header_from(freelist_header_is_freed(header_next), header_new.curr_block_size,
315 header_next->curr_block_size);
316 }
317 header_new.__prev_block_size_w_freed_bit |= 1;
318 assert(freelist_header_should_be_in_freetree(&header_new));
319
320 struct freetree_node *node_addr = (struct freetree_node *)header_addr;
321 freetree_node_init(node_addr, header_new);
322 freetree_insert_node(&self->rb_rootptr, node_addr);
323}
Align memory.
static uintptr_t calc_alignment_padding(const size_t alignment, const uintptr_t ptr)
Calculate the alignment padding required to align a pointer.
Definition align.h:91
#define CALC_ALIGNMENT_PADDING(alignment, ptr)
compile time calc_alignment_padding.
Definition align.h:105
Intrusive red-black tree.
Freelist header definition. This lies at the front of every block.
Definition freelist.h:37
size_t __prev_block_size_w_freed_bit
Definition freelist.h:38
size_t curr_block_size
Current block size.
Definition freelist.h:40
Freelist struct definition.
Definition freelist.h:56
struct freetree_node * rb_rootptr
Header of freetree (freed memory blocks)
Definition freelist.h:60
unsigned char * buf_ptr
Underlying buffer.
Definition freelist.h:61
size_t buf_used
Number of bytes used of buffer.
Definition freelist.h:58
struct freelist_header * head
Header of freelist headers (all memory blocks)
Definition freelist.h:59
size_t buf_len
Buffer length.
Definition freelist.h:57