Arena initial implementation.

Signed-off-by: Mitja Horvat <mitja@plume.com>
This commit is contained in:
Mitja Horvat
2024-11-08 08:49:22 +01:00
commit ebea1803c9
5 changed files with 526 additions and 0 deletions

209
src/arena.c Normal file
View File

@ -0,0 +1,209 @@
#include <sys/mman.h>
#include <assert.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
#include "arena.h"
#if __STDC_VERSION__ < 201112L /* below C11 */
typedef intptr_t max_align_t;
#define thread_local __thread
#endif
static void arena_defer_cleanup(arena_t *arena);
arena_t *arena_new(size_t sz)
{
arena_t *arena;
size_t msz = sz + sizeof(arena_t);
if (msz < sysconf(_SC_PAGE_SIZE))
{
arena = malloc(msz);
}
else
{
arena = mmap(NULL, msz, PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);
if (arena == MAP_FAILED) return false;
}
arena->a_sz = sz;
arena->a_pos = 0;
return arena;
}
void arena_del(arena_t *arena)
{
size_t msz = arena->a_sz + sizeof(arena_t);
arena_pull(arena, SIZE_MAX);
memset(arena, 0, sizeof(*arena));
if (msz < sysconf(_SC_PAGE_SIZE))
{
free(arena);
}
else if (munmap(arena, msz) != 0)
{
fprintf(stderr, "munmap() failed: %s\n", strerror(errno));
}
}
/*
* Extend the arena boundary by `sz` bytes.
* Can be used to grow the last allocated element.
*/
void *arena_push(arena_t *arena, size_t sz)
{
void *ret = &arena->a_data[arena->a_pos];
if ((SIZE_MAX - arena->a_pos) < sz ||
arena->a_pos + sz > arena->a_sz)
{
return NULL;
}
arena->a_pos += sz;
return ret;
}
/*
* Reduce the arena boundary by `sz` bytes.
*/
void arena_pull(arena_t *arena, size_t sz)
{
if (arena->a_pos < sz)
{
arena->a_pos = 0;
}
else
{
arena->a_pos -= sz;
}
arena_defer_cleanup(arena);
}
/*
* Save current arena boundary, calling arena_restore() afterwards will
* free everything that was allocated between arena_save() and arena_restore()
*/
arena_frame_t arena_save(arena_t *arena)
{
if (arena == NULL) return (arena_frame_t){ .af_arena = NULL };
return (arena_frame_t){ .af_arena = arena, .af_pos = arena->a_pos };
}
/*
* Restore saved arena boundary (free elements between arena_save() and arena_restore())
*/
void arena_restore(arena_frame_t *frame)
{
if (frame->af_arena == NULL) return;
if (frame->af_pos > frame->af_arena->a_pos) return;
arena_pull(frame->af_arena, frame->af_arena->a_pos - frame->af_pos);
}
/*
* Allocate a defer action on the current arena. If the defer action block
* is freed, the defer callback is called.
*/
bool arena_defer(arena_t *arena, arena_defer_fn_t *fn, void *data)
{
struct arena_defer *defer;
defer = arena_malloc(arena, sizeof(*defer));
if (defer == NULL)
{
fprintf(stderr, "Unable to allocate defer buffer.");
fn(arena, data);
return false;
}
defer->ad_fn = fn;
defer->ad_data = data;
defer->ad_next = arena->a_defer;
defer->ad_magic = ARENA_MAGIC;
defer->ad_magic ^= (uintptr_t)defer;
defer->ad_magic ^= (uintptr_t)defer->ad_next;
defer->ad_magic ^= (uintptr_t)defer->ad_fn;
defer->ad_magic ^= (uintptr_t)defer->ad_data;
arena->a_defer = defer;
return true;
}
void arena_defer_cleanup(arena_t *arena)
{
while (arena->a_defer != NULL)
{
if (((uint8_t *)arena->a_defer + sizeof(struct arena_defer)) <= &arena->a_data[arena->a_pos])
break;
uintptr_t magic = arena->a_defer->ad_magic;
magic ^= (uintptr_t)arena->a_defer;
magic ^= (uintptr_t)arena->a_defer->ad_next;
magic ^= (uintptr_t)arena->a_defer->ad_fn;
magic ^= (uintptr_t)arena->a_defer->ad_data;
if (magic != ARENA_MAGIC)
{
assert(!"Defer buffer corrupted.");
}
arena->a_defer->ad_fn(arena, arena->a_defer->ad_data);
arena->a_defer = arena->a_defer->ad_next;
}
}
void *arena_malign(arena_t *arena, size_t sz)
{
size_t off;
size_t align = 1;
/* Calculate the alignment of sz */
while (align < sz)
{
align <<= 1;
if (align >= sizeof(max_align_t)) break;
}
/* Calculate the offset we need to add to the current position */
off = align;
off -= arena->a_pos & (align - 1);
off &= (align - 1);
if (arena_push(arena, off) == NULL) return NULL;
return &arena->a_data[arena->a_pos];
}
void *arena_malloc(arena_t *arena, size_t sz)
{
if (arena_malign(arena, sz) == NULL) return NULL;
return arena_push(arena, sz);
}
/*
* Allocate a string on the arena
*/
char *arena_strdup(arena_t *arena, const char *src)
{
char *dst;
size_t slen = strlen(src);
dst = arena_push(arena, slen);
if (dst == NULL) return NULL;
return strcpy(dst, src);
}

80
src/arena.h Normal file
View File

@ -0,0 +1,80 @@
#if !defined(ARENA_H_INCLUDED)
#define ARENA_H_INCLUDED
#include <stddef.h>
#include <stdint.h>
#include <stdbool.h>
#define ARENA_MAGIC (0xAA55AA55AA55AA55)
/* Default scratch arena size */
#define ARENA_SCRATCH_DEFAULT_SIZE (2*1024*1024)
#define __V(x, ...) __VA_ARGS__
#define ARENA_VPACK(...) ((arena_t *[]){ __V(dummy, ##__VA_ARGS__, NULL) })
#if defined(__GNUC__)
#define CLEANUP(...) __attribute__((cleanup(__VA_ARGS__)))
#endif
/*
* Automatically free arena when ARENA_AUTO goes out of scope, this is equivalent
* of calling arena_save() at the beginning of the current scope and
* arena_restore() at the end.
*
* This uses a GNU C specific extension.
*/
#define ARENA_AUTO(x) \
arena_frame_t CLEANUP(arena_restore) __cleanup_ ## x = arena_save(x);
/*
* Get a scratch arena and automatically free it. `...` in this context is an
* array of conflicting arenas. scratch_get() ensures that `x` is not a
* previously allocated scratchpad arena that is present in the conflict list
*/
#define ARENA_SCRATCH(x, ...) \
arena_t *x = arena_scratch(__VA_ARGS__); \
ARENA_AUTO(x)
typedef struct arena arena_t;
typedef struct arena_frame arena_frame_t;
typedef void arena_defer_fn_t(arena_t *, void *data);
struct arena
{
size_t a_pos; /* Current arena position */
size_t a_sz; /* Total arena size */
struct arena_defer *a_defer; /* List of deferes */
uint8_t a_data[0]; /* Arena data */
};
struct arena_frame
{
arena_t *af_arena;
size_t af_pos;
};
struct arena_defer
{
uintptr_t ad_magic;
arena_defer_fn_t *ad_fn;
void *ad_data;
void *ad_next;
};
arena_t *arena_new(size_t sz);
void arena_del(arena_t *arena);
void *arena_push(arena_t *arena, size_t sz);
void arena_pull(arena_t *arena, size_t sz);
arena_frame_t arena_save(arena_t *arena);
void arena_restore(arena_frame_t *frame);
bool arena_defer(arena_t *arena, arena_defer_fn_t *fn, void *data);
void *arena_malloc(arena_t *arena, size_t sz);
char *arena_strdup(arena_t *arena, const char *src);
/* Wrapper around __arena_scratch() */
#define arena_scratch(...) __arena_scratch(ARENA_VPACK(__VA_ARGS__))
arena_t *__arena_scratch(arena_t **conflicts);
void arena_scratch_destroy(void);
void arena_scratch_init(void);
#endif /* ARENA_H_INCLUDED */

81
src/arena_scratch.c Normal file
View File

@ -0,0 +1,81 @@
#include <stdio.h>
#include <stdlib.h>
#include "arena.h"
#if __STDC_VERSION__ >= 201112L /* C11 and above */
#define thread_local _Thread_local
#elif defined(__GNUC__)
#define thread_local __thread
#else
#error Unsupported compiler.
#endif
#if defined(__GNUC__)
#define WEAK __attribute__((weak))
#endif
thread_local arena_t *arena_scratch_list[4] = { 0 };
void arena_scratch_destroy(void)
{
for (int s = 0; s < sizeof(arena_scratch_list) / sizeof(arena_scratch_list[0]); s++)
{
if (arena_scratch_list[s] == NULL) continue;
arena_del(arena_scratch_list[s]);
arena_scratch_list[s] = NULL;
}
}
/*
* Optionally allocate and return a scratch arena. Make sure that the returned
* arena is not present in the NULL-terminated list `conflicts`
*/
arena_t *__arena_scratch(arena_t **conflicts)
{
int s = 0;
if (conflicts != NULL)
{
for (s = 0; s < sizeof(arena_scratch_list) / sizeof(arena_scratch_list[0]); s++)
{
arena_t **c = conflicts;
for (; *c != NULL; c++)
{
if (*c == arena_scratch_list[s]) break;
}
/* No conflcits found, break out */
if (*c == NULL) break;
}
if (s >= sizeof(arena_scratch_list) / sizeof(arena_scratch_list[0]))
{
fprintf(stderr, "Out of scratch arenas.\n");
return NULL;
}
}
if (arena_scratch_list[s] == NULL)
{
arena_scratch_init();
arena_scratch_list[s] = arena_new(ARENA_SCRATCH_DEFAULT_SIZE);
if (arena_scratch_list[s] == NULL)
{
fprintf(stderr, "Error allocating scratch arena %d.", s);
return NULL;
}
printf("NEW SCRATCH = %p\n", arena_scratch_list[s]);
}
return arena_scratch_list[s];
}
void WEAK arena_scratch_init(void)
{
static bool init = false;
for (;!init; init = true)
{
printf("Single-threaded mode.\n");
atexit(arena_scratch_destroy);
}
}

27
src/arena_thread.h Normal file
View File

@ -0,0 +1,27 @@
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include "arena.h"
void arena_scratch_thread_cleanup(void *p)
{
(void)p;
arena_scratch_destroy();
}
pthread_key_t scratch_thread_key;
void arena_scratch_init_once(void)
{
printf("Multi-threaded mode.\n");
pthread_key_create(&scratch_thread_key, arena_scratch_thread_cleanup);
atexit(arena_scratch_destroy);
}
void arena_scratch_init(void)
{
static pthread_once_t once = PTHREAD_ONCE_INIT;
(void)pthread_once(&once, arena_scratch_init_once);
pthread_setspecific(scratch_thread_key, (void *)0xdeadbeef);
}

129
src/main.c Normal file
View File

@ -0,0 +1,129 @@
#include <dlfcn.h>
#include <stdio.h>
#include <unistd.h>
#include <pthread.h>
#include "arena.h"
#include "arena_thread.h"
#define TEST_FILE "/etc/os-release"
void foo(arena_t *ar, const char *str)
{
ARENA_SCRATCH(arena, ar);
arena_push(arena, 11);
printf("Got: %s\n", str);
}
void test(void)
{
ARENA_SCRATCH(arena);
foo(arena, arena_strdup(arena, "hello world"));
}
void arena_defer_test(arena_t *arena, void *data)
{
(void)data;
(void)arena;
printf("Cleanup!\n");
}
void arena_defer_close_fn(arena_t *arena, void *data)
{
int fd = (intptr_t)data;
printf("Closing file descriptor: %d\n", fd);
close(fd);
}
bool arena_defer_close(arena_t *arena, int fd)
{
return arena_defer(arena, arena_defer_close_fn, (void *)(uintptr_t)fd);
}
void arena_defer_fclose_fn(arena_t *arena, void *data)
{
printf("Closing file: %p\n", data);
fclose((FILE *)data);
}
bool arena_defer_fclose(arena_t *arena, FILE *f)
{
return arena_defer(arena, arena_defer_fclose_fn, f);
}
char *get_file(arena_t *arena, const char *path)
{
FILE *f;
char *rc;
size_t nr;
char *buf;
if ((f = fopen(path, "r")) == NULL)
{
return NULL;
}
if (!arena_defer_fclose(arena, f))
{
return NULL;
}
buf = rc = arena_push(arena, 16);
while ((nr = fread(buf, 1, 16, f)) >= 16)
{
buf = arena_push(arena, 16);
}
buf[nr] = '\0';
return rc;
}
void cleanup(void *p)
{
(void)p;
printf("THREAD CLEANUP!\n");
}
pthread_key_t key;
void thr_defer_test(arena_t *a, void *t)
{
(void)t;
printf("DEFER WORKS\n");
}
void *thr_main(void *p)
{
arena_t *sc = arena_scratch();
arena_defer(sc, thr_defer_test, NULL);
printf("> Going out of thread\n");
return NULL;
}
int main(void)
{
char *file;
ARENA_SCRATCH(scratch);
file = get_file(scratch, TEST_FILE);
if (file == NULL)
{
printf("Error reading file: %s\n", TEST_FILE);
}
printf("FILE: %s\n", file);
printf("NEW thread\n");
pthread_t thr;
pthread_create(&thr, NULL, thr_main, NULL);
printf("Joining thread ...\n");
pthread_join(thr, NULL);
printf("THREAD EXIT!\n");
arena_t *sc = arena_scratch(scratch);
arena_defer(sc, thr_defer_test, NULL);
return 0;
}