summaryrefslogtreecommitdiff
path: root/src/core/arena_allocator.c
blob: cbde03826544b0be483a3368e1d737ea9e1e4430 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
#include "arena_allocator.h"

#include <assert.h>
#include <stddef.h>
#include <stdbool.h>

SandArena sand_create_arena(SandAllocator *a) {
	return (SandArena) {
		.parent = a,
		.current_region = NULL,
	};
}

#define MAX(a, b) ({ \
		__auto_type _a = (a);\
		__auto_type _b = (b);\
		_a > _b ? _a : _b;\
		})

#define REGION_DEFUALT_SIZE ((size_t)2048)

static SandRegion *allocate_new_region(SandAllocator *a, size_t size, size_t alignment) {
	size_t padded_size = size + (alignment - 1);
	size_t capacity = MAX(padded_size, REGION_DEFUALT_SIZE) ;
	size_t region_size = offsetof(SandRegion, data) + capacity;
	SandRegion *result = sand_allocate_aligned(a, region_size, _Alignof(SandRegion));
	if (result == NULL) {
		return NULL;
	}
	result->capacity = capacity;
	result->used = 0;
	return result;
}

static void *allocate(size_t size, size_t alignment, void *user_data) {
	SandArena *arena = user_data;

	// This is the first allocation made within the arena. Let's allocate the initial region.
	if (arena->current_region == NULL) {
		SandRegion *first_region = allocate_new_region(arena->parent, size, alignment);
		if (first_region == NULL) {
			return NULL;
		}
		first_region->prev = NULL;
		arena->current_region = first_region;
	}

	SandRegion *iter = arena->current_region;
	while (true) { // Loop looking for a region.
		void *aligned_ptr = sand_align_pointer_forward(iter->data + iter->used, alignment);
		void *end_ptr = iter->data + iter->capacity;
		if (aligned_ptr + size > end_ptr) { // This pointer region doesn't have room; allocate a new one and try again.
			SandRegion *new_region = allocate_new_region(arena->parent, size, alignment);
			if (new_region == NULL) {
				return NULL;
			}

			new_region->prev = arena->current_region;
			arena->current_region = iter;

			iter = new_region; // Next time is guaranteed to succeed.
			continue;
		} else { // Yay, we found a spot!
			iter->used += aligned_ptr - (void *)iter->data;
			return aligned_ptr;
		}
	}

	assert(false); // unreachable
}

static void deallocate(void *old_ptr, size_t old_size, void *user_data) {
	(void)old_ptr;
	(void)old_size;
	(void)user_data;
	return; // No-op, as all allocations are freed when the arena is freed.
}

// Returns a `SandAllocator` which is valid as long as the arena is.
// The arena must not be moved afterwards, as the allocator retains a pointer to the arena.
SandAllocator sand_get_allocator_for_arena(SandArena *arena) {
	return (SandAllocator) {
		.allocate = allocate,
		.reallocate = NULL, // TODO: Special case for if it is the last allocation of the current region.
		.deallocate = deallocate,
		.user_data = arena,
	};
}

void sand_destroy_arena(SandArena *arena) {
	SandRegion *iter = arena->current_region;
	while (iter != NULL) {
		SandRegion *next = iter->prev;
		size_t region_size = offsetof(SandRegion, data) + iter->capacity;
		sand_deallocate(arena->parent, iter, region_size);
		iter = next;
	}
}