Full rewrite

This commit is contained in:
Ole Morud
2023-06-17 12:14:28 +02:00
committed by Ole Morud
parent e5fa743d85
commit 069f6b11dc
9 changed files with 176 additions and 272 deletions

View File

@@ -1,2 +1,10 @@
BasedOnStyle: WebKit BasedOnStyle: WebKit
AlignAfterOpenBracket: Align
AlignConsecutiveAssignments: true
AlignConsecutiveDeclarations: true
AlignEscapedNewlines: Left
AlignOperands: true
AlignTrailingComments: false
PointerAlignment: Right

1
.gitignore vendored
View File

@@ -3,3 +3,4 @@ test/
*.o *.o
*.a *.a
*.so *.so
build/

15
CMakeLists.txt Normal file
View File

@@ -0,0 +1,15 @@
cmake_minimum_required(VERSION 3.16)
project("ArenaAllocator"
VERSION 1.0
LANGUAGES C
DESCRIPTION "Arena Allocator in C"
)
add_subdirectory(src)
if(CMAKE_PROJECT_NAME STREQUAL PROJECT_NAME)
message(STATUS "Building tests")
add_subdirectory(test)
endif()

View File

@@ -1,72 +0,0 @@
all:
BUILD ?= debug
COMPILER ?= gcc
# ==== set compiler flags ====
# credits Maxim Egorushkin:
# https://stackoverflow.com/questions/48791883/best-practice-for-building-a-make-file/48793058#48793058
CC := $(COMPILER)
# -fsanitize={address,undefined}
CFLAGS.gcc.debug := -Og -ggdb -pg -fsanitize=address -fno-omit-frame-pointer
CFLAGS.gcc.release := -O3 -g -march=native -DNDEBUG
CFLAGS.gcc := -fanalyzer
CFLAGS.clang.debug :=-O0 -g3 -DBACKTRACE -rdynamic
CFLAGS.clang.release :=-O3 -g -march=native -DNDEBUG
CFLAGS.clang :=
CFLAGS := ${CFLAGS.${COMPILER}} ${CFLAGS.${COMPILER}.${BUILD}} \
-Iinclude \
-std=c2x \
-Wall -Wextra -Wpedantic -Werror
BUILD_DIR := build
BIN_DIR := lib
TEST_DIR := test
OBJS := $(BUILD_DIR)/arena.o
FPIC_OBJS := $(BUILD_DIR)/fpic/arena.o
all : static
static : $(BIN_DIR)/libarena.a
dynamic : $(BIN_DIR)/libarena.so
tests : test/test_arena
$(TEST_DIR)/test_arena : src/test_arena.c $(BIN_DIR)/libarena.a | $(TEST_DIR)
$(CC) -o $@ $(CFLAGS) $^
$(BIN_DIR)/libarena.a : $(OBJS) | $(BIN_DIR)
ar cr $@ $^
$(BIN_DIR)/libarena.so : $(FPIC_OBJS) | $(BIN_DIR)
$(CC) -o $@ -shared $(CFLAGS) $^
$(BUILD_DIR)/%.o : src/%.c | $(BUILD_DIR)
$(CC) -o $@ -c $(CFLAGS) $<
$(BUILD_DIR)/fpic/%.o : src/%.c | $(BUILD_DIR)/fpic
$(CC) -o $@ -c -fPIC $(CFLAGS) $<
$(BUILD_DIR):
mkdir -p $@
$(BUILD_DIR)/fpic:
mkdir -p $@
$(BIN_DIR) :
mkdir -p $@
$(TEST_DIR) :
mkdir -p $@
clean:
rm -rf $(BUILD_DIR) $(BIN_DIR) $(TEST_DIR)
.PHONY: clean $(BUILD_DIR) test bin all

View File

@@ -1,13 +1,38 @@
## `arena_t arena_new()`
Allocate a new arena.
The underlying memory is allocated with mmap.
`arena_t* arena_new()` ## `void arena_delete(arena_t *a)`
Initializes and returns new arena Delete memory mapped for arena.
Should only be used with arenas from arena\_new().
`void arena_reset(arena_t *a)`
Free memory allocated in arena ## `arena_t arena_attach(void *ptr, size_t size)`
`void* arena_alloc(arena_t *a, size_t len)` Attach an arena to an existing memory region.
The arena will not expand the region if space is exceeded.
## `void *arena_detatch(arena_t arena)`
Detach an arena from an existing memory region.
## `void arena_reset(arena_t *a)`
Reset an arena.
## `void *arena_alloc(arena_t *a, size_t len)`
Allocate memory from an arena.
Returns NULL and sets errno on failure.
## `void *arena_calloc(arena_t *a, size_t nmemb, size_t size)`
Allocate and zero memory from an arena.
Returns NULL and sets errno on failure.
Allocate new memory using arena

View File

@@ -2,23 +2,60 @@
#ifndef ARENA_H #ifndef ARENA_H
#define ARENA_H #define ARENA_H
#include <stddef.h> // ptrdiff_t #include <stdbool.h>
#include <stdint.h> // uintptr_t #include <stddef.h> // size_t
#define _WORD_SIZE (sizeof(intptr_t))
// linked list terminology:
// https://web.archive.org/web/20230604145332/https://i.stack.imgur.com/2FbXf.png
typedef struct arena { typedef struct arena {
struct page *head, *last; char *data;
} __attribute__((aligned(_WORD_SIZE))) arena_t; size_t size;
size_t cap;
bool grow;
} arena_t;
arena_t arena_new(void); /**
void arena_reset(arena_t* a); * Allocate a new arena.
void* arena_alloc(arena_t* a, size_t len); * The underlying memory is allocated with mmap.
void* arena_calloc(arena_t* a, size_t nmemb, size_t size); */
void* arena_realloc_tail(arena_t* a, size_t len); arena_t arena_new();
void arena_delete(arena_t* a);
void arena_free(arena_t* a, void* p); /**
* Delete memory mapped for arena.
* Should only be used with arenas from arena_new().
*/
void arena_delete(arena_t *a);
/**
* Attach an arena to an existing memory region.
* The arena will not expand the region if space is exceeded.
*/
static inline arena_t arena_attach(void *ptr, size_t size)
{
return (arena_t) { .data = ptr, .size = 0, .cap = size, .grow = false };
}
/**
* Detach an arena from an existing memory region.
*/
static inline void *arena_detatch(arena_t arena)
{
return arena.data;
}
/**
* Reset an arena.
*/
void arena_reset(arena_t *a);
/**
* Allocate memory from an arena.
* Returns NULL and sets errno on failure.
*/
void *arena_alloc(arena_t *a, size_t len);
/**
* Allocate and zero memory from an arena.
* Returns NULL and sets errno on failure.
*/
void *arena_calloc(arena_t *a, size_t nmemb, size_t size);
#endif #endif

4
src/CMakeLists.txt Normal file
View File

@@ -0,0 +1,4 @@
add_library(arena_allocator arena.c)
target_include_directories(arena_allocator PUBLIC ../include)

View File

@@ -1,153 +1,81 @@
#include "arena.h" #include "arena.h"
#include <errno.h> #include <errno.h> // errno
#include <stdint.h> #include <stdbool.h>
#include <stdlib.h> #include <stdio.h> // fprintf
#include <string.h> #include <string.h> // strerror
#include <sys/mman.h>
#include <unistd.h> #include <unistd.h>
// (sizeof(intptr_t) isn't guaranteed to be the machine word size but on most #define ARENA_ALIGN (sizeof(void *))
// compilers it is) #define ARENA_GROW_FACTOR 2UL
#define SYS_PAGE_SIZE ((size_t)sysconf(_SC_PAGE_SIZE))
#define BIG_PAGE (SYS_PAGE_SIZE + 1) #ifndef NDEBUG
#define arena_err(msg) \
fprintf(stderr, "%s (%s:%d): %s\n", msg, __func__, __LINE__, strerror(errno))
#else
#define arena_err(msg)
#endif
typedef unsigned char byte_t; static bool arena_grow(struct arena *a)
struct page {
void* data;
size_t offset, prev_offset;
struct page* next;
};
// linked list terminology:
// https://web.archive.org/web/20230604145332/https://i.stack.imgur.com/2FbXf.png
arena_t arena_new(void)
{ {
struct page* p = malloc(sizeof *p); int ok = mprotect(
a->data + a->cap,
a->cap * ARENA_GROW_FACTOR,
PROT_READ | PROT_WRITE);
if (ok == -1) {
arena_err("mprotect");
return false;
}
a->cap *= ARENA_GROW_FACTOR;
return true;
}
inline void arena_reset(arena_t *a)
{
a->size = 0;
}
arena_t arena_new()
{
size_t size = sysconf(_SC_PAGE_SIZE);
void *p = mmap(NULL, 1UL << 40UL, PROT_NONE, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);
mprotect(p, size, PROT_READ | PROT_WRITE);
if (p == MAP_FAILED)
arena_err("mmap");
return arena_attach(p, size);
}
void *arena_alloc(arena_t *a, size_t size)
{
size = (size + ARENA_ALIGN - 1) & ~(ARENA_ALIGN - 1); // align
void *p = a->data + a->size;
a->size += size;
if (a->size > a->cap) {
if (!arena_grow(a))
return NULL;
}
return p;
}
void *arena_calloc(arena_t *a, size_t nmemb, size_t size)
{
void *p = arena_alloc(a, nmemb * size);
if (p == NULL) if (p == NULL)
exit(errno); return p;
p->next = NULL;
p->offset = 0;
p->prev_offset = 0;
p->data = malloc(SYS_PAGE_SIZE);
if (p->data == NULL)
exit(errno);
arena_t a = {
.head = p,
.last = p,
};
return a;
}
void _arena_new_page(arena_t* a, size_t size)
{
/* potentially reuse page from previously
reset arena */
if (a->head->next != NULL) {
a->head = a->head->next;
a->head->offset = 0;
a->head->prev_offset = 0;
return;
}
void* tmp = calloc(1, sizeof *(a->head->next));
if (tmp == NULL)
exit(errno);
a->head->next = tmp;
a->head = a->head->next;
a->head->data = malloc(size);
if (a->head->data == NULL)
exit(errno);
}
void arena_reset(arena_t* a)
{
a->head = a->last;
a->head->offset = 0;
a->head->prev_offset = 0;
}
void* _arena_big_alloc(arena_t* a, size_t size)
{
_arena_new_page(a, size);
a->head->offset = BIG_PAGE;
a->head->prev_offset = BIG_PAGE;
return a->head->data;
}
void* arena_alloc(arena_t* a, size_t size)
{
if (size > SYS_PAGE_SIZE)
return _arena_big_alloc(a, size);
// align size to machine word size
size = (size + _WORD_SIZE - 1) & ~(_WORD_SIZE - 1);
if (a->head->offset > SYS_PAGE_SIZE - size) {
_arena_new_page(a, SYS_PAGE_SIZE);
return arena_alloc(a, size);
}
a->head->prev_offset = a->head->offset;
a->head->offset += size;
return (byte_t*)(a->head->data) + a->head->prev_offset;
}
void* arena_calloc(arena_t* a, size_t nmemb, size_t size)
{
void* p = arena_alloc(a, nmemb * size);
memset(p, 0, nmemb * size); memset(p, 0, nmemb * size);
return p; return p;
} }
void* arena_realloc_tail(arena_t* a, size_t len) void arena_delete(struct arena *a)
{ {
if (a->head->offset == BIG_PAGE) { munmap(a->data, a->cap);
void* tmp = realloc(a->head->data, len); a->cap = -1;
a->size = -1;
if (tmp == NULL)
exit(errno);
a->head->data = tmp;
return tmp;
}
a->head->offset = a->head->prev_offset;
return arena_alloc(a, len);
}
void arena_delete(arena_t* a)
{
struct page* p = a->last;
while (p != NULL) {
struct page* next = p->next;
free(p->data);
free(p);
p = next;
}
}
// included for completeness
void arena_free(arena_t* a, void* p)
{
(void)p;
(void)a;
return;
} }

View File

@@ -1,42 +0,0 @@
// _start test_arena_alloc.c
#include "arena.h"
#include <err.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main()
{
arena_t default_arena = arena_new();
printf("\nAttempting to allocate and write to 1 byte 1024 times");
for (size_t i = 0; i < 1024; i++) {
char* c = arena_alloc(&default_arena, sizeof *c);
if (c == NULL)
err(EXIT_FAILURE, "failed to allocate memory");
*c = i & 0xFF;
}
printf("\n OK!\n");
printf("\nAttempting to allocate and write to `_SC_PAGESIZE+1` bytes");
size_t psz = sysconf(_SC_PAGESIZE) + 1;
char* c = arena_alloc(&default_arena, psz);
if (c == NULL)
err(EXIT_FAILURE, "failed to allocate memory");
for (size_t i = 0; i < psz; i++)
*c = i & 0xFF;
printf("\n OK!\n");
arena_delete(&default_arena);
return 0;
}