Hawaii Hybrid
Loading...
Searching...
No Matches
arena.h
Go to the documentation of this file.
1// Copyright 2022 Alexey Kutepov <reximkut@gmail.com>
2
3// Permission is hereby granted, free of charge, to any person obtaining
4// a copy of this software and associated documentation files (the
5// "Software"), to deal in the Software without restriction, including
6// without limitation the rights to use, copy, modify, merge, publish,
7// distribute, sublicense, and/or sell copies of the Software, and to
8// permit persons to whom the Software is furnished to do so, subject to
9// the following conditions:
10
11// The above copyright notice and this permission notice shall be
12// included in all copies or substantial portions of the Software.
13
14// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21
22#ifndef ARENA_H_
23#define ARENA_H_
24
25#include <stddef.h>
26#include <stdint.h>
27
28#ifndef ARENA_NOSTDIO
29#include <stdarg.h>
30#include <stdio.h>
31#endif // ARENA_NOSTDIO
32
33#ifndef ARENA_ASSERT
34#include <assert.h>
35#define ARENA_ASSERT assert
36#endif
37
38#define ARENA_BACKEND_LIBC_MALLOC 0
39#define ARENA_BACKEND_LINUX_MMAP 1
40#define ARENA_BACKEND_WIN32_VIRTUALALLOC 2
41#define ARENA_BACKEND_WASM_HEAPBASE 3
42
43#ifndef ARENA_BACKEND
44#define ARENA_BACKEND ARENA_BACKEND_LIBC_MALLOC
45#endif // ARENA_BACKEND
46
47typedef struct Region Region;
48
49struct Region {
51 size_t count;
52 size_t capacity;
53 uintptr_t data[];
54};
55
56typedef struct {
58} Arena;
59
60typedef struct {
62 size_t count;
64
65#ifndef ARENA_REGION_DEFAULT_CAPACITY
66#define ARENA_REGION_DEFAULT_CAPACITY (8*1024)
67#endif // ARENA_REGION_DEFAULT_CAPACITY
68
69Region *new_region(size_t capacity);
71
72void *arena_alloc(Arena *a, size_t size_bytes);
73void *arena_realloc(Arena *a, void *oldptr, size_t oldsz, size_t newsz);
74char *arena_strdup(Arena *a, const char *cstr);
75void *arena_memdup(Arena *a, void *data, size_t size);
76void *arena_memcpy(void *dest, const void *src, size_t n);
77#ifndef ARENA_NOSTDIO
78char *arena_sprintf(Arena *a, const char *format, ...);
79char *arena_vsprintf(Arena *a, const char *format, va_list args);
80#endif // ARENA_NOSTDIO
81
87
88#ifndef ARENA_DA_INIT_CAP
89#define ARENA_DA_INIT_CAP 256
90#endif // ARENA_DA_INIT_CAP
91
92#ifdef __cplusplus
93 #define cast_ptr(ptr) (decltype(ptr))
94#else
95 #define cast_ptr(...)
96#endif
97
98#define arena_da_append(a, da, item) \
99 do { \
100 if ((da)->count >= (da)->capacity) { \
101 size_t new_capacity = (da)->capacity == 0 ? ARENA_DA_INIT_CAP : (da)->capacity*2; \
102 (da)->items = cast_ptr((da)->items)arena_realloc( \
103 (a), (da)->items, \
104 (da)->capacity*sizeof(*(da)->items), \
105 new_capacity*sizeof(*(da)->items)); \
106 (da)->capacity = new_capacity; \
107 } \
108 \
109 (da)->items[(da)->count++] = (item); \
110 } while (0)
111
112// Append several items to a dynamic array
113#define arena_da_append_many(a, da, new_items, new_items_count) \
114 do { \
115 if ((da)->count + (new_items_count) > (da)->capacity) { \
116 size_t new_capacity = (da)->capacity; \
117 if (new_capacity == 0) new_capacity = ARENA_DA_INIT_CAP; \
118 while ((da)->count + (new_items_count) > new_capacity) new_capacity *= 2; \
119 (da)->items = cast_ptr((da)->items)arena_realloc( \
120 (a), (da)->items, \
121 (da)->capacity*sizeof(*(da)->items), \
122 new_capacity*sizeof(*(da)->items)); \
123 (da)->capacity = new_capacity; \
124 } \
125 arena_memcpy((da)->items + (da)->count, (new_items), (new_items_count)*sizeof(*(da)->items)); \
126 (da)->count += (new_items_count); \
127 } while (0)
128
129// Append a sized buffer to a string builder
130#define arena_sb_append_buf arena_da_append_many
131
132// Append a NULL-terminated string to a string builder
133#define arena_sb_append_cstr(a, sb, cstr) \
134 do { \
135 const char *s = (cstr); \
136 size_t n = arena_strlen(s); \
137 arena_da_append_many(a, sb, s, n); \
138 } while (0)
139
140// Append a single NULL character at the end of a string builder. So then you can
141// use it a NULL-terminated C string
142#define arena_sb_append_null(a, sb) arena_da_append(a, sb, 0)
143
144#endif // ARENA_H_
145
146#ifdef ARENA_IMPLEMENTATION
147
148#if ARENA_BACKEND == ARENA_BACKEND_LIBC_MALLOC
149#include <stdlib.h>
150
151// TODO: instead of accepting specific capacity new_region() should accept the size of the object we want to fit into the region
152// It should be up to new_region() to decide the actual capacity to allocate
153Region *new_region(size_t capacity)
154{
155 size_t size_bytes = sizeof(Region) + sizeof(uintptr_t)*capacity;
156 // TODO: it would be nice if we could guarantee that the regions are allocated by ARENA_BACKEND_LIBC_MALLOC are page aligned
157 Region *r = (Region*)malloc(size_bytes);
158 ARENA_ASSERT(r); // TODO: since ARENA_ASSERT is disableable go through all the places where we use it to check for failed memory allocation and return with NULL there.
159 r->next = NULL;
160 r->count = 0;
161 r->capacity = capacity;
162 return r;
163}
164
165void free_region(Region *r)
166{
167 free(r);
168}
169#elif ARENA_BACKEND == ARENA_BACKEND_LINUX_MMAP
170#include <unistd.h>
171#include <sys/mman.h>
172
173Region *new_region(size_t capacity)
174{
175 size_t size_bytes = sizeof(Region) + sizeof(uintptr_t) * capacity;
176 Region *r = mmap(NULL, size_bytes, PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);
177 ARENA_ASSERT(r != MAP_FAILED);
178 r->next = NULL;
179 r->count = 0;
180 r->capacity = capacity;
181 return r;
182}
183
184void free_region(Region *r)
185{
186 size_t size_bytes = sizeof(Region) + sizeof(uintptr_t) * r->capacity;
187 int ret = munmap(r, size_bytes);
188 ARENA_ASSERT(ret == 0);
189}
190
191#elif ARENA_BACKEND == ARENA_BACKEND_WIN32_VIRTUALALLOC
192
193#if !defined(_WIN32)
194# error "Current platform is not Windows"
195#endif
196
197#define WIN32_LEAN_AND_MEAN
198#include <windows.h>
199
200#define INV_HANDLE(x) (((x) == NULL) || ((x) == INVALID_HANDLE_VALUE))
201
202Region *new_region(size_t capacity)
203{
204 SIZE_T size_bytes = sizeof(Region) + sizeof(uintptr_t) * capacity;
205 Region *r = VirtualAllocEx(
206 GetCurrentProcess(), /* Allocate in current process address space */
207 NULL, /* Unknown position */
208 size_bytes, /* Bytes to allocate */
209 MEM_COMMIT | MEM_RESERVE, /* Reserve and commit allocated page */
210 PAGE_READWRITE /* Permissions ( Read/Write )*/
211 );
212 if (INV_HANDLE(r))
213 ARENA_ASSERT(0 && "VirtualAllocEx() failed.");
214
215 r->next = NULL;
216 r->count = 0;
217 r->capacity = capacity;
218 return r;
219}
220
221void free_region(Region *r)
222{
223 if (INV_HANDLE(r))
224 return;
225
226 BOOL free_result = VirtualFreeEx(
227 GetCurrentProcess(), /* Deallocate from current process address space */
228 (LPVOID)r, /* Address to deallocate */
229 0, /* Bytes to deallocate ( Unknown, deallocate entire page ) */
230 MEM_RELEASE /* Release the page ( And implicitly decommit it ) */
231 );
232
233 if (FALSE == free_result)
234 ARENA_ASSERT(0 && "VirtualFreeEx() failed.");
235}
236
237#elif ARENA_BACKEND == ARENA_BACKEND_WASM_HEAPBASE
238
239// Stolen from https://surma.dev/things/c-to-webassembly/
240
241extern unsigned char __heap_base;
242// Since ARENA_BACKEND_WASM_HEAPBASE entirely hijacks __heap_base it is expected that no other means of memory
243// allocation are used except the arenas.
244unsigned char* bump_pointer = &__heap_base;
245// TODO: provide a way to deallocate all the arenas at once by setting bump_pointer back to &__heap_base?
246
247// __builtin_wasm_memory_size and __builtin_wasm_memory_grow are defined in units of page sizes
248#define ARENA_WASM_PAGE_SIZE (64*1024)
249
250Region *new_region(size_t capacity)
251{
252 size_t size_bytes = sizeof(Region) + sizeof(uintptr_t)*capacity;
253 Region *r = (void*)bump_pointer;
254
255 // grow memory brk() style
256 size_t current_memory_size = ARENA_WASM_PAGE_SIZE * __builtin_wasm_memory_size(0);
257 size_t desired_memory_size = (size_t) bump_pointer + size_bytes;
258 if (desired_memory_size > current_memory_size) {
259 size_t delta_bytes = desired_memory_size - current_memory_size;
260 size_t delta_pages = (delta_bytes + (ARENA_WASM_PAGE_SIZE - 1))/ARENA_WASM_PAGE_SIZE;
261 if (__builtin_wasm_memory_grow(0, delta_pages) < 0) {
262 ARENA_ASSERT(0 && "memory.grow failed");
263 return NULL;
264 }
265 }
266
267 bump_pointer += size_bytes;
268
269 r->next = NULL;
270 r->count = 0;
271 r->capacity = capacity;
272 return r;
273}
274
275void free_region(Region *r)
276{
277 // Since ARENA_BACKEND_WASM_HEAPBASE uses a primitive bump allocator to
278 // allocate the regions, free_region() does nothing. It is generally
279 // not recommended to free arenas anyway since it is better to keep
280 // reusing already allocated memory with arena_reset().
281 (void) r;
282}
283
284#else
285# error "Unknown Arena backend"
286#endif
287
288// TODO: add debug statistic collection mode for arena
289// Should collect things like:
290// - How many times new_region was called
291// - How many times existing region was skipped
292// - How many times allocation exceeded ARENA_REGION_DEFAULT_CAPACITY
293
294void *arena_alloc(Arena *a, size_t size_bytes)
295{
296 size_t size = (size_bytes + sizeof(uintptr_t) - 1)/sizeof(uintptr_t);
297
298 if (a->end == NULL) {
299 ARENA_ASSERT(a->begin == NULL);
300 size_t capacity = ARENA_REGION_DEFAULT_CAPACITY;
301 if (capacity < size) capacity = size;
302 a->end = new_region(capacity);
303 a->begin = a->end;
304 }
305
306 while (a->end->count + size > a->end->capacity && a->end->next != NULL) {
307 a->end = a->end->next;
308 }
309
310 if (a->end->count + size > a->end->capacity) {
311 ARENA_ASSERT(a->end->next == NULL);
312 size_t capacity = ARENA_REGION_DEFAULT_CAPACITY;
313 if (capacity < size) capacity = size;
314 a->end->next = new_region(capacity);
315 a->end = a->end->next;
316 }
317
318 void *result = &a->end->data[a->end->count];
319 a->end->count += size;
320 return result;
321}
322
323void *arena_realloc(Arena *a, void *oldptr, size_t oldsz, size_t newsz)
324{
325 if (newsz <= oldsz) return oldptr;
326 void *newptr = arena_alloc(a, newsz);
327 char *newptr_char = (char*)newptr;
328 char *oldptr_char = (char*)oldptr;
329 for (size_t i = 0; i < oldsz; ++i) {
330 newptr_char[i] = oldptr_char[i];
331 }
332 return newptr;
333}
334
335size_t arena_strlen(const char *s)
336{
337 size_t n = 0;
338 while (*s++) n++;
339 return n;
340}
341
342void *arena_memcpy(void *dest, const void *src, size_t n)
343{
344 char *d = (char*) dest;
345 const char *s = (const char*) src;
346 for (; n; n--) *d++ = *s++;
347 return dest;
348}
349
350char *arena_strdup(Arena *a, const char *cstr)
351{
352 size_t n = arena_strlen(cstr);
353 char *dup = (char*)arena_alloc(a, n + 1);
354 arena_memcpy(dup, cstr, n);
355 dup[n] = '\0';
356 return dup;
357}
358
359void *arena_memdup(Arena *a, void *data, size_t size)
360{
361 return arena_memcpy(arena_alloc(a, size), data, size);
362}
363
364#ifndef ARENA_NOSTDIO
365char *arena_vsprintf(Arena *a, const char *format, va_list args)
366{
367 va_list args_copy;
368 va_copy(args_copy, args);
369 int n = vsnprintf(NULL, 0, format, args_copy);
370 va_end(args_copy);
371
372 ARENA_ASSERT(n >= 0);
373 char *result = (char*)arena_alloc(a, n + 1);
374 vsnprintf(result, n + 1, format, args);
375
376 return result;
377}
378
379char *arena_sprintf(Arena *a, const char *format, ...)
380{
381 va_list args;
382 va_start(args, format);
383 char *result = arena_vsprintf(a, format, args);
384 va_end(args);
385
386 return result;
387}
388#endif // ARENA_NOSTDIO
389
391{
392 Arena_Mark m;
393 if(a->end == NULL){ //snapshot of uninitialized arena
394 ARENA_ASSERT(a->begin == NULL);
395 m.region = a->end;
396 m.count = 0;
397 }else{
398 m.region = a->end;
399 m.count = a->end->count;
400 }
401
402 return m;
403}
404
405void arena_reset(Arena *a)
406{
407 for (Region *r = a->begin; r != NULL; r = r->next) {
408 r->count = 0;
409 }
410
411 a->end = a->begin;
412}
413
414void arena_rewind(Arena *a, Arena_Mark m)
415{
416 if(m.region == NULL){ //snapshot of uninitialized arena
417 arena_reset(a); //leave allocation
418 return;
419 }
420
421 m.region->count = m.count;
422 for (Region *r = m.region->next; r != NULL; r = r->next) {
423 r->count = 0;
424 }
425
426 a->end = m.region;
427}
428
429void arena_free(Arena *a)
430{
431 Region *r = a->begin;
432 while (r) {
433 Region *r0 = r;
434 r = r->next;
435 free_region(r0);
436 }
437 a->begin = NULL;
438 a->end = NULL;
439}
440
441void arena_trim(Arena *a){
442 Region *r = a->end->next;
443 while (r) {
444 Region *r0 = r;
445 r = r->next;
446 free_region(r0);
447 }
448 a->end->next = NULL;
449}
450
451#endif // ARENA_IMPLEMENTATION
void * arena_alloc(Arena *a, size_t size_bytes)
void arena_trim(Arena *a)
void free_region(Region *r)
#define ARENA_ASSERT
Definition arena.h:35
void arena_reset(Arena *a)
char * arena_vsprintf(Arena *a, const char *format, va_list args)
void arena_free(Arena *a)
void arena_rewind(Arena *a, Arena_Mark m)
Arena_Mark arena_snapshot(Arena *a)
void * arena_realloc(Arena *a, void *oldptr, size_t oldsz, size_t newsz)
Region * new_region(size_t capacity)
void * arena_memcpy(void *dest, const void *src, size_t n)
char * arena_strdup(Arena *a, const char *cstr)
char * arena_sprintf(Arena *a, const char *format,...)
void * arena_memdup(Arena *a, void *data, size_t size)
#define ARENA_REGION_DEFAULT_CAPACITY
Definition hawaii.c:2
Definition arena.h:60
size_t count
Definition arena.h:62
Region * region
Definition arena.h:61
Definition arena.h:56
Region * end
Definition arena.h:57
Region * begin
Definition arena.h:57
Definition arena.h:49
size_t count
Definition arena.h:51
uintptr_t data[]
Definition arena.h:53
Region * next
Definition arena.h:50
size_t capacity
Definition arena.h:52