Arena initial implementation.
Signed-off-by: Mitja Horvat <mitja@plume.com>
This commit is contained in:
209
src/arena.c
Normal file
209
src/arena.c
Normal 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
80
src/arena.h
Normal 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
81
src/arena_scratch.c
Normal 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
27
src/arena_thread.h
Normal 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
129
src/main.c
Normal 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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user