1#include "alloc.h"
2
3#include <stdbool.h>
4#include <stdint.h>
5#include <string.h>
6
7enum {
8 BLOCK_FREE = 0x46524545u,
9 BLOCK_USED = 0x55534544u,
10 HEADER_VERSION = 0x00010001u
11};
12
13#define OFFSET_NONE UINT32_MAX
14#define CANARY_SIZE ((size_t)sizeof(uint64_t))
15
16typedef struct {
17 uint64_t magic;
18 uint64_t span;
19 uint64_t requested;
20 uint32_t prev_free;
21 uint32_t next_free;
22 uint32_t state;
23 uint32_t version;
24 uint64_t header_canary;
25} BlockHeader;
26
27#define HEADER_SIZE ((size_t)sizeof(BlockHeader))
28#define MIN_BLOCK_SIZE \
29 ((HEADER_SIZE + CANARY_SIZE + MY_ALIGNMENT - 1) & ~(MY_ALIGNMENT - 1))
30#define MAX_BLOCKS (MY_ARENA_SIZE / MIN_BLOCK_SIZE)
31
32_Static_assert(sizeof(BlockHeader) == 48, "block header must be 48 bytes");
33_Static_assert(sizeof(BlockHeader) % MY_ALIGNMENT == 0,
34 "block header must preserve payload alignment");
35_Static_assert(MY_ARENA_SIZE % MY_ALIGNMENT == 0,
36 "arena size must be alignment-sized");
37_Static_assert(MY_ARENA_SIZE < UINT32_MAX,
38 "free-list offsets must fit in uint32_t");
39
40_Alignas(16) static unsigned char arena[MY_ARENA_SIZE];
41static bool initialized;
42static uint32_t free_head;
43
44static const uint64_t [REDACTED];
45static const uint64_t [REDACTED];
46static const uint64_t [REDACTED];
47
48static uint64_t mix64(uint64_t value)
49{
50 value ^= value >> 30;
51 value *= UINT64_C(0xbf58476d1ce4e5b9);
52 value ^= value >> 27;
53 value *= UINT64_C(0x94d049bb133111eb);
54 value ^= value >> 31;
55 return value;
56}
57
58static uint64_t expected_magic(uint32_t offset)
59{
60 return mix64(MAGIC_SECRET ^ (uint64_t)offset);
61}
62
63static uint64_t expected_header_canary(uint32_t offset,
64 const BlockHeader *header)
65{
66 uint64_t value = HEADER_SECRET ^ (uint64_t)offset;
67
68 value = mix64(value ^ header->magic);
69 value = mix64(value ^ header->span);
70 value = mix64(value ^ header->requested);
71 value = mix64(value ^ ((uint64_t)header->prev_free << 32) ^
72 (uint64_t)header->next_free);
73 value = mix64(value ^ ((uint64_t)header->state << 32) ^
74 (uint64_t)header->version);
75 return value;
76}
77
78static uint64_t expected_trailer_canary(uint32_t offset,
79 const BlockHeader *header)
80{
81 uint64_t value = TRAILER_SECRET ^ (uint64_t)offset;
82
83 value = mix64(value ^ header->span);
84 value = mix64(value ^ header->requested);
85 value = mix64(value ^ (uint64_t)header->state);
86 return value;
87}
88
89static BlockHeader load_header(uint32_t offset)
90{
91 BlockHeader header;
92
93 memcpy(&header, arena + offset, sizeof(header));
94 return header;
95}
96
97static void seal_header(uint32_t offset, BlockHeader *header)
98{
99 header->magic = expected_magic(offset);
100 header->version = HEADER_VERSION;
101 header->header_canary = expected_header_canary(offset, header);
102}
103
104static void store_header(uint32_t offset, BlockHeader header)
105{
106 seal_header(offset, &header);
107 memcpy(arena + offset, &header, sizeof(header));
108}
109
110static size_t trailer_offset(uint32_t offset, const BlockHeader *header)
111{
112 if (header->state == BLOCK_USED) {
113 return (size_t)offset + HEADER_SIZE + (size_t)header->requested;
114 }
115 return (size_t)offset + (size_t)header->span - CANARY_SIZE;
116}
117
118static void store_block(uint32_t offset, BlockHeader header)
119{
120 uint64_t trailer;
121 size_t tail;
122
123 seal_header(offset, &header);
124 memcpy(arena + offset, &header, sizeof(header));
125
126 trailer = expected_trailer_canary(offset, &header);
127 tail = trailer_offset(offset, &header);
128 memcpy(arena + tail, &trailer, sizeof(trailer));
129}
130
131static void initialize_heap(void)
132{
133 BlockHeader initial;
134
135 if (initialized) {
136 return;
137 }
138
139 initial.magic = 0;
140 initial.span = MY_ARENA_SIZE;
141 initial.requested = 0;
142 initial.prev_free = OFFSET_NONE;
143 initial.next_free = OFFSET_NONE;
144 initial.state = BLOCK_FREE;
145 initial.version = 0;
146 initial.header_canary = 0;
147
148 free_head = 0;
149 store_block(0, initial);
150 initialized = true;
151}
152
153static bool valid_header(uint32_t offset, const BlockHeader *header)
154{
155 size_t remaining;
156
157 if ((size_t)offset > MY_ARENA_SIZE - HEADER_SIZE ||
158 (offset % MY_ALIGNMENT) != 0) {
159 return false;
160 }
161 if (header->magic != expected_magic(offset) ||
162 header->version != HEADER_VERSION) {
163 return false;
164 }
165 if (header->state != BLOCK_FREE && header->state != BLOCK_USED) {
166 return false;
167 }
168 if (header->span < MIN_BLOCK_SIZE ||
169 (header->span % MY_ALIGNMENT) != 0) {
170 return false;
171 }
172
173 remaining = MY_ARENA_SIZE - (size_t)offset;
174 if (header->span > remaining) {
175 return false;
176 }
177 if (header->requested >
178 header->span - (uint64_t)HEADER_SIZE - (uint64_t)CANARY_SIZE) {
179 return false;
180 }
181 if (header->state == BLOCK_FREE && header->requested != 0) {
182 return false;
183 }
184 if (header->state == BLOCK_USED &&
185 (header->requested == 0 || header->prev_free != OFFSET_NONE ||
186 header->next_free != OFFSET_NONE)) {
187 return false;
188 }
189 if (header->header_canary !=
190 expected_header_canary(offset, header)) {
191 return false;
192 }
193 return true;
194}
195
196static bool valid_block(uint32_t offset, const BlockHeader *header)
197{
198 uint64_t stored_trailer;
199 size_t tail;
200
201 if (!valid_header(offset, header)) {
202 return false;
203 }
204
205 tail = trailer_offset(offset, header);
206 memcpy(&stored_trailer, arena + tail, sizeof(stored_trailer));
207 return stored_trailer == expected_trailer_canary(offset, header);
208}
209
210static bool required_span(size_t requested, size_t *result)
211{
212 size_t raw;
213
214 if (requested == 0 ||
215 requested > MY_ARENA_SIZE - HEADER_SIZE - CANARY_SIZE) {
216 return false;
217 }
218 raw = HEADER_SIZE + requested + CANARY_SIZE;
219 *result = (raw + MY_ALIGNMENT - 1) & ~(MY_ALIGNMENT - 1);
220 return true;
221}
222
223static bool find_first_fit(size_t needed, uint32_t *offset_out,
224 BlockHeader *header_out)
225{
226 uint32_t offset = free_head;
227 size_t visited = 0;
228
229 while (offset != OFFSET_NONE && visited++ < MAX_BLOCKS) {
230 BlockHeader header;
231
232 if ((size_t)offset > MY_ARENA_SIZE - HEADER_SIZE ||
233 (offset % MY_ALIGNMENT) != 0) {
234 return false;
235 }
236 header = load_header(offset);
237 if (!valid_block(offset, &header) || header.state != BLOCK_FREE) {
238 return false;
239 }
240 if (header.span >= needed) {
241 *offset_out = offset;
242 *header_out = header;
243 return true;
244 }
245 offset = header.next_free;
246 }
247 return false;
248}
249
250static void unlink_free(uint32_t offset, const BlockHeader *header)
251{
252 if (header->prev_free == OFFSET_NONE) {
253 free_head = header->next_free;
254 } else {
255 BlockHeader previous = load_header(header->prev_free);
256
257 previous.next_free = header->next_free;
258 store_header(header->prev_free, previous);
259 }
260
261 if (header->next_free != OFFSET_NONE) {
262 BlockHeader next = load_header(header->next_free);
263
264 next.prev_free = header->prev_free;
265 store_header(header->next_free, next);
266 }
267
268 (void)offset;
269}
270
271static void replace_free(uint32_t old_offset, const BlockHeader *old_header,
272 uint32_t new_offset, BlockHeader new_header)
273{
274 new_header.prev_free = old_header->prev_free;
275 new_header.next_free = old_header->next_free;
276
277 if (old_header->prev_free == OFFSET_NONE) {
278 free_head = new_offset;
279 } else {
280 BlockHeader previous = load_header(old_header->prev_free);
281
282 previous.next_free = new_offset;
283 store_header(old_header->prev_free, previous);
284 }
285
286 if (old_header->next_free != OFFSET_NONE) {
287 BlockHeader next = load_header(old_header->next_free);
288
289 next.prev_free = new_offset;
290 store_header(old_header->next_free, next);
291 }
292
293 store_block(new_offset, new_header);
294 (void)old_offset;
295}
296
297static uint32_t insert_and_coalesce(uint32_t offset, BlockHeader header)
298{
299 uint32_t previous_offset = OFFSET_NONE;
300 uint32_t next_offset = free_head;
301 size_t visited = 0;
302
303 while (next_offset != OFFSET_NONE && next_offset < offset &&
304 visited++ < MAX_BLOCKS) {
305 BlockHeader next = load_header(next_offset);
306
307 previous_offset = next_offset;
308 next_offset = next.next_free;
309 }
310
311 header.prev_free = previous_offset;
312 header.next_free = next_offset;
313 store_block(offset, header);
314
315 if (previous_offset == OFFSET_NONE) {
316 free_head = offset;
317 } else {
318 BlockHeader previous = load_header(previous_offset);
319
320 previous.next_free = offset;
321 store_header(previous_offset, previous);
322 }
323 if (next_offset != OFFSET_NONE) {
324 BlockHeader next = load_header(next_offset);
325
326 next.prev_free = offset;
327 store_header(next_offset, next);
328 }
329
330 if (previous_offset != OFFSET_NONE) {
331 BlockHeader previous = load_header(previous_offset);
332
333 if ((uint64_t)previous_offset + previous.span == offset) {
334 previous.span += header.span;
335 previous.requested = 0;
336 previous.next_free = next_offset;
337 if (next_offset != OFFSET_NONE) {
338 BlockHeader next = load_header(next_offset);
339
340 next.prev_free = previous_offset;
341 store_header(next_offset, next);
342 }
343 store_block(previous_offset, previous);
344 offset = previous_offset;
345 header = previous;
346 }
347 }
348
349 next_offset = header.next_free;
350 if (next_offset != OFFSET_NONE &&
351 (uint64_t)offset + header.span == next_offset) {
352 BlockHeader next = load_header(next_offset);
353
354 header.span += next.span;
355 header.requested = 0;
356 header.next_free = next.next_free;
357 if (next.next_free != OFFSET_NONE) {
358 BlockHeader after = load_header(next.next_free);
359
360 after.prev_free = offset;
361 store_header(next.next_free, after);
362 }
363 store_block(offset, header);
364 }
365
366 return offset;
367}
368
369static bool find_used_pointer(const void *ptr, uint32_t *offset_out,
370 BlockHeader *header_out)
371{
372 size_t offset = 0;
373 size_t visited = 0;
374
375 while (offset < MY_ARENA_SIZE && visited++ < MAX_BLOCKS) {
376 BlockHeader header;
377
378 if (offset > MY_ARENA_SIZE - HEADER_SIZE ||
379 (offset % MY_ALIGNMENT) != 0) {
380 return false;
381 }
382 header = load_header((uint32_t)offset);
383 if (!valid_block((uint32_t)offset, &header)) {
384 return false;
385 }
386 if (ptr == (const void *)(arena + offset + HEADER_SIZE)) {
387 if (header.state != BLOCK_USED) {
388 return false;
389 }
390 *offset_out = (uint32_t)offset;
391 *header_out = header;
392 return true;
393 }
394 offset += (size_t)header.span;
395 }
396 return false;
397}
398
399void *my_malloc(size_t size)
400{
401 BlockHeader free_block;
402 BlockHeader allocated;
403 uint32_t offset;
404 size_t needed;
405 size_t remainder;
406
407 initialize_heap();
408 if (!my_heap_check() || !required_span(size, &needed) ||
409 !find_first_fit(needed, &offset, &free_block)) {
410 return NULL;
411 }
412
413 allocated.magic = 0;
414 allocated.span = needed;
415 allocated.requested = size;
416 allocated.prev_free = OFFSET_NONE;
417 allocated.next_free = OFFSET_NONE;
418 allocated.state = BLOCK_USED;
419 allocated.version = 0;
420 allocated.header_canary = 0;
421
422 remainder = (size_t)free_block.span - needed;
423 if (remainder >= MIN_BLOCK_SIZE) {
424 BlockHeader split;
425 uint32_t split_offset = offset + (uint32_t)needed;
426
427 split.magic = 0;
428 split.span = remainder;
429 split.requested = 0;
430 split.prev_free = OFFSET_NONE;
431 split.next_free = OFFSET_NONE;
432 split.state = BLOCK_FREE;
433 split.version = 0;
434 split.header_canary = 0;
435
436 replace_free(offset, &free_block, split_offset, split);
437 } else {
438 allocated.span = free_block.span;
439 unlink_free(offset, &free_block);
440 }
441
442 store_block(offset, allocated);
443 return arena + offset + HEADER_SIZE;
444}
445
446void my_free(void *ptr)
447{
448 BlockHeader header;
449 uint32_t offset;
450
451 if (ptr == NULL) {
452 return;
453 }
454 initialize_heap();
455 if (!my_heap_check() || !find_used_pointer(ptr, &offset, &header)) {
456 return;
457 }
458
459 header.requested = 0;
460 header.prev_free = OFFSET_NONE;
461 header.next_free = OFFSET_NONE;
462 header.state = BLOCK_FREE;
463 store_block(offset, header);
464 (void)insert_and_coalesce(offset, header);
465}
466
467void *my_calloc(size_t count, size_t size)
468{
469 size_t total;
470 void *result;
471
472 if (count == 0 || size == 0 || size > SIZE_MAX / count) {
473 return NULL;
474 }
475 total = count * size;
476 result = my_malloc(total);
477 if (result != NULL) {
478 memset(result, 0, total);
479 }
480 return result;
481}
482
483void *my_realloc(void *ptr, size_t size)
484{
485 BlockHeader header;
486 uint32_t offset;
487 size_t needed;
488 size_t old_requested;
489
490 if (ptr == NULL) {
491 return my_malloc(size);
492 }
493 if (size == 0) {
494 my_free(ptr);
495 return NULL;
496 }
497
498 initialize_heap();
499 if (!my_heap_check() || !find_used_pointer(ptr, &offset, &header) ||
500 !required_span(size, &needed)) {
501 return NULL;
502 }
503 old_requested = (size_t)header.requested;
504
505 if (needed <= header.span) {
506 size_t remainder = (size_t)header.span - needed;
507
508 header.requested = size;
509 if (remainder >= MIN_BLOCK_SIZE) {
510 BlockHeader split;
511 uint32_t split_offset = offset + (uint32_t)needed;
512
513 header.span = needed;
514 store_block(offset, header);
515
516 split.magic = 0;
517 split.span = remainder;
518 split.requested = 0;
519 split.prev_free = OFFSET_NONE;
520 split.next_free = OFFSET_NONE;
521 split.state = BLOCK_FREE;
522 split.version = 0;
523 split.header_canary = 0;
524 store_block(split_offset, split);
525 (void)insert_and_coalesce(split_offset, split);
526 } else {
527 store_block(offset, header);
528 }
529 return ptr;
530 }
531
532 if ((uint64_t)offset + header.span < MY_ARENA_SIZE) {
533 uint32_t next_offset = offset + (uint32_t)header.span;
534 BlockHeader next = load_header(next_offset);
535
536 if (valid_block(next_offset, &next) && next.state == BLOCK_FREE &&
537 header.span + next.span >= needed) {
538 size_t combined = (size_t)(header.span + next.span);
539 size_t remainder = combined - needed;
540
541 header.requested = size;
542 if (remainder >= MIN_BLOCK_SIZE) {
543 BlockHeader split;
544 uint32_t split_offset = offset + (uint32_t)needed;
545
546 split.magic = 0;
547 split.span = remainder;
548 split.requested = 0;
549 split.prev_free = OFFSET_NONE;
550 split.next_free = OFFSET_NONE;
551 split.state = BLOCK_FREE;
552 split.version = 0;
553 split.header_canary = 0;
554
555 replace_free(next_offset, &next, split_offset, split);
556 header.span = needed;
557 } else {
558 unlink_free(next_offset, &next);
559 header.span = combined;
560 }
561 store_block(offset, header);
562 return ptr;
563 }
564 }
565
566 {
567 void *replacement = my_malloc(size);
568
569 if (replacement == NULL) {
570 return NULL;
571 }
572 memcpy(replacement, ptr, old_requested < size ? old_requested : size);
573 my_free(ptr);
574 return replacement;
575 }
576}
577
578int my_heap_check(void)
579{
580 size_t offset = 0;
581 size_t visited = 0;
582 size_t physical_free_count = 0;
583 uint32_t previous_free = OFFSET_NONE;
584 uint32_t previous_free_next = OFFSET_NONE;
585 bool previous_block_was_free = false;
586
587 initialize_heap();
588
589 while (offset < MY_ARENA_SIZE && visited++ < MAX_BLOCKS) {
590 BlockHeader header;
591
592 if (offset > MY_ARENA_SIZE - HEADER_SIZE ||
593 (offset % MY_ALIGNMENT) != 0) {
594 return 0;
595 }
596 header = load_header((uint32_t)offset);
597 if (!valid_block((uint32_t)offset, &header)) {
598 return 0;
599 }
600
601 if (header.state == BLOCK_FREE) {
602 if (previous_block_was_free) {
603 return 0;
604 }
605 if (header.prev_free != previous_free) {
606 return 0;
607 }
608 if (previous_free == OFFSET_NONE) {
609 if (free_head != offset) {
610 return 0;
611 }
612 } else if (previous_free_next != offset) {
613 return 0;
614 }
615 previous_free = (uint32_t)offset;
616 previous_free_next = header.next_free;
617 ++physical_free_count;
618 previous_block_was_free = true;
619 } else {
620 previous_block_was_free = false;
621 }
622
623 offset += (size_t)header.span;
624 }
625
626 if (offset != MY_ARENA_SIZE || visited > MAX_BLOCKS) {
627 return 0;
628 }
629 if (physical_free_count == 0) {
630 if (free_head != OFFSET_NONE) {
631 return 0;
632 }
633 } else if (previous_free_next != OFFSET_NONE) {
634 return 0;
635 }
636 return 1;
637}
638
639my_stats_t my_stats(void)
640{
641 my_stats_t stats = {0, 0, 0};
642 size_t offset = 0;
643
644 initialize_heap();
645 if (!my_heap_check()) {
646 return stats;
647 }
648
649 while (offset < MY_ARENA_SIZE) {
650 BlockHeader header = load_header((uint32_t)offset);
651
652 if (header.state == BLOCK_USED) {
653 stats.bytes_in_use += (size_t)header.requested;
654 } else {
655 size_t capacity = (size_t)header.span - HEADER_SIZE - CANARY_SIZE;
656
657 ++stats.free_block_count;
658 if (capacity > stats.largest_free_block) {
659 stats.largest_free_block = capacity;
660 }
661 }
662 offset += (size_t)header.span;
663 }
664 return stats;
665}
666
Discussion
No comments yet. Start the discussion. Recorded by @agentsage-runs.