aboutsummaryrefslogtreecommitdiff
path: root/lib/alloc/arena_alloc.c
diff options
context:
space:
mode:
Diffstat (limited to 'lib/alloc/arena_alloc.c')
-rw-r--r--lib/alloc/arena_alloc.c77
1 files changed, 77 insertions, 0 deletions
diff --git a/lib/alloc/arena_alloc.c b/lib/alloc/arena_alloc.c
new file mode 100644
index 0000000..e5b99b7
--- /dev/null
+++ b/lib/alloc/arena_alloc.c
@@ -0,0 +1,77 @@
+#include <sys/mman.h>
+
+#include <errno.h>
+#include <stdckdint.h>
+#include <stdlib.h>
+
+#include "_attrs.h"
+#include "alloc.h"
+#include "macros.h"
+
+#define IS_POW_2(n) ((n) != 0 && ((n) & ((n) - 1)) == 0)
+
+[[_mlib_pure, _mlib_inline]] static size_t pad(size_t, size_t);
+static struct _region *mkregion(size_t);
+
+size_t
+pad(size_t len, size_t align)
+{
+ return (len + align - 1) & ~(align - 1);
+}
+
+struct _region *
+mkregion(size_t cap)
+{
+ struct _region *r = malloc(sizeof(struct _region));
+ if (r == nullptr)
+ return nullptr;
+ *r = (struct _region){
+ .cap = cap,
+ .data = mmap(nullptr, cap, PROT_READ | PROT_WRITE,
+ MAP_PRIVATE | MAP_ANONYMOUS, -1, 0),
+ };
+ if (r->data == MAP_FAILED) {
+ free(r);
+ return nullptr;
+ }
+ return r;
+}
+
+void *
+arena_alloc(arena *a, size_t sz, size_t n, size_t align)
+{
+ ASSUME(a != nullptr);
+ ASSUME(IS_POW_2(align));
+
+ if (ckd_mul(&sz, sz, n)) {
+ errno = EOVERFLOW;
+ return nullptr;
+ }
+
+ for (struct _region *r = a->_head; r != nullptr; r = r->next) {
+ size_t off = pad(r->len, align);
+
+ /* Technically there are other ways to solve this… but at this point you
+ might as well just fail */
+ size_t padded_sz;
+ if (ckd_add(&padded_sz, off, sz)) {
+ errno = EOVERFLOW;
+ return nullptr;
+ }
+
+ if (padded_sz <= r->cap) {
+ void *ret = (char *)r->data + off;
+ r->len = off + sz;
+ return ret;
+ }
+ }
+
+ /* No page exists with enough space */
+ struct _region *r = mkregion(MAX(sz, a->_init));
+ if (r == nullptr)
+ return nullptr;
+ r->next = a->_head;
+ r->len = sz;
+ a->_head = r;
+ return r->data;
+}