From 30cc709b8cdbeaf4a83088eea16262f646ad15f4 Mon Sep 17 00:00:00 2001 From: "Sven M. Hallberg" <pesco@khjk.org> Date: Sun, 27 Dec 2015 14:45:21 +0100 Subject: [PATCH] add SLOB allocator --- src/SConscript | 6 +- src/hammer.h | 3 + src/sloballoc.c | 216 +++++++++++++++++++++++++++++++++++++++++++++++ src/sloballoc.h | 15 ++++ src/t_mm.c | 148 ++++++++++++++++++++++++++++++++ src/test_suite.c | 2 + src/test_suite.h | 1 + 7 files changed, 389 insertions(+), 2 deletions(-) create mode 100644 src/sloballoc.c create mode 100644 src/sloballoc.h create mode 100644 src/t_mm.c diff --git a/src/SConscript b/src/SConscript index 7a1b9d49..3d254520 100644 --- a/src/SConscript +++ b/src/SConscript @@ -66,7 +66,8 @@ misc_hammer_parts = [ 'platform_bsdlike.c', 'pprint.c', 'registry.c', - 'system_allocator.c'] + 'system_allocator.c', + 'sloballoc.c'] ctests = ['t_benchmark.c', 't_bitreader.c', @@ -74,7 +75,8 @@ ctests = ['t_benchmark.c', 't_parser.c', 't_grammar.c', 't_misc.c', - 't_regression.c'] + 't_mm.c', + 't_regression.c'] libhammer_shared = env.SharedLibrary('hammer', parsers + backends + misc_hammer_parts) libhammer_static = env.StaticLibrary('hammer', parsers + backends + misc_hammer_parts) diff --git a/src/hammer.h b/src/hammer.h index 1be297c7..6c2bf497 100644 --- a/src/hammer.h +++ b/src/hammer.h @@ -789,6 +789,9 @@ HTokenType h_get_token_type_number(const char* name); const char* h_get_token_type_name(HTokenType token_type); // }}} +/// Make an allocator that draws from the given memory area. +HAllocator *h_sloballoc(void *mem, size_t size); + #ifdef __cplusplus } #endif diff --git a/src/sloballoc.c b/src/sloballoc.c new file mode 100644 index 00000000..8b6cbc5e --- /dev/null +++ b/src/sloballoc.c @@ -0,0 +1,216 @@ +// first-fit SLOB (simple list of blocks) allocator + +#include "sloballoc.h" +#include <stdint.h> +#include <assert.h> + +struct alloc { + size_t size; + uint8_t data[]; +}; + +struct block { + struct alloc alloc; + struct block *next; +}; + +struct slob { + size_t size; + struct block *head; + uint8_t data[]; +}; + + +SLOB *slobinit(void *mem, size_t size) +{ + SLOB *slob = mem; + + assert(size >= sizeof(SLOB) + sizeof(struct block)); + assert(size < UINTPTR_MAX - (uintptr_t)mem); + + slob = mem; + slob->size = size - sizeof(SLOB); + slob->head = mem + sizeof(SLOB); + slob->head->alloc.size = slob->size - sizeof(struct alloc); + slob->head->next = NULL; + + return slob; +} + +void *sloballoc(SLOB *slob, size_t size) +{ + struct block *b, **p; + size_t fitblock, remblock; + + // size must be enough to extend to a struct block in case of free + fitblock = sizeof(struct block) - sizeof(struct alloc); + if(size < fitblock) size = fitblock; + + // need this much to fit another block in the remaining space + remblock = size + sizeof(struct block); + if(remblock < size) return NULL; // overflow + + // scan list for the first block of sufficient size + for(p=&slob->head; (b=*p); p=&b->next) { + if(b->alloc.size >= remblock) { + // cut from the end of the block + b->alloc.size -= sizeof(struct alloc) + size; + struct alloc *a = (void *)b->alloc.data + b->alloc.size; + a->size = size; + return a->data; + } else if(b->alloc.size >= size) { + // when a block fills, it converts directly to a struct alloc + *p = b->next; // unlink + return b->alloc.data; + } + } + + return NULL; +} + +void slobfree(SLOB *slob, void *a_) +{ + struct alloc *a = a_ - sizeof(struct alloc); + struct block *b, **p, *left=NULL, *right=NULL, **rightp; + + // sanity check: a lies inside slob + assert((void *)a >= (void *)slob->data); + assert((void *)a->data + a->size <= (void *)slob->data + slob->size); + + // scan list for blocks adjacent to a + for(p=&slob->head; (b=*p); p=&b->next) { + if((void *)a == b->alloc.data + b->alloc.size) { + assert(!left); + left = b; + } + if((void *)a->data + a->size == b) { + assert(!right); + right = b; + rightp = p; + } + + if(left && right) { + // extend left and unlink right + left->alloc.size += sizeof(*a) + a->size + + sizeof(right->alloc) + right->alloc.size; + *rightp = right->next; + return; + } + } + + if(left) { + // extend left to absorb a + left->alloc.size += sizeof(*a) + a->size; + } else if(right) { + // shift and extend right to absorb a + right->alloc.size += sizeof(*a) + a->size; + *rightp = (struct block *)a; **rightp = *right; + } else { + // spawn new block over a + struct block *b = (struct block *)a; + b->next = slob->head; slob->head = b; + } +} + +int slobcheck(SLOB *slob) +{ + // invariants: + // 1. memory area is divided seamlessly and exactly into n blocks + // 2. every block is large enough to hold a 'struct block'. + // 3. free list has at most n elements. + // 4. every element of the free list is one of the valid blocks. + // 5. every block appears at most once in the free list. + + void *p; + size_t nblocks=0, nfree=0; + + #define FORBLOCKS \ + for(p = slob->data; \ + p != slob->data + slob->size; \ + p += sizeof(struct alloc) + ((struct alloc *)p)->size) + + // 1. memory area is divided seamlessly and exactly into n blocks + FORBLOCKS { + if(p < (void *)slob->data) + return 1; + if(p > (void *)slob->data + slob->size) + return 2; + nblocks++; + + struct alloc *a = p; + if(a->size > UINTPTR_MAX - (uintptr_t)p) + return 3; + + // 2. every block is large enough to hold a 'struct block'. + if(a->size + sizeof(struct alloc) < sizeof(struct block)) + return 4; + } + + // 3. free list has at most n elements. + for(struct block *b=slob->head; b; b=b->next) { + nfree++; + if(nfree > nblocks) + return 5; + + // 4. every element of the free list is one of the valid blocks. + FORBLOCKS + if(p == b) break; + if(!p) + return 6; + } + + // 5. every block appears at most once in the free list. + FORBLOCKS { + size_t count=0; + for(struct block *b=slob->head; b; b=b->next) + if(p == b) count++; + if(count > 1) + return 7; + } + + #undef FORBLOCKS + return 0; +} + + +// hammer interface + +#include "hammer.h" + +static void *h_slob_alloc(HAllocator *mm, size_t size) +{ + SLOB *slob = (SLOB *)(mm+1); + return sloballoc(slob, size); +} + +static void h_slob_free(HAllocator *mm, void *p) +{ + SLOB *slob = (SLOB *)(mm+1); + slobfree(slob, p); +} + +static void *h_slob_realloc(HAllocator *mm, void *p, size_t size) +{ + SLOB *slob = (SLOB *)(mm+1); + + assert(((void)"XXX need realloc for SLOB allocator", 0)); + return NULL; +} + +HAllocator *h_sloballoc(void *mem, size_t size) +{ + if(size < sizeof(HAllocator)) + return NULL; + + HAllocator *mm = mem; + SLOB *slob = slobinit(mem + sizeof(HAllocator), size - sizeof(HAllocator)); + if(!slob) + return NULL; + assert(slob == (SLOB *)(mm+1)); + + mm->alloc = h_slob_alloc; + mm->realloc = h_slob_realloc; + mm->free = h_slob_free; + + return mm; +} diff --git a/src/sloballoc.h b/src/sloballoc.h new file mode 100644 index 00000000..ecdc479e --- /dev/null +++ b/src/sloballoc.h @@ -0,0 +1,15 @@ +#ifndef SLOBALLOC_H_SEEN +#define SLOBALLOC_H_SEEN + +#include <stddef.h> + +typedef struct slob SLOB; + +SLOB *slobinit(void *mem, size_t size); +void *sloballoc(SLOB *slob, size_t size); +void slobfree(SLOB *slob, void *p); + +// consistency check (verify internal invariants); returns 0 on success +int slobcheck(SLOB *slob); + +#endif // SLOBALLOC_H_SEEN diff --git a/src/t_mm.c b/src/t_mm.c new file mode 100644 index 00000000..620d4e80 --- /dev/null +++ b/src/t_mm.c @@ -0,0 +1,148 @@ +#include <glib.h> +#include <string.h> +#include "test_suite.h" +#include "sloballoc.h" +#include "hammer.h" + +#define check_sloballoc_invariants() do { \ + int err = slobcheck(slob); \ + if(err) { \ + g_test_message("SLOB invariant check failed on line %d, returned %d", \ + __LINE__, err); \ + g_test_fail(); \ + } \ + } while(0) + +#define check_sloballoc(VAR, SIZE, OFFSET) do { \ + check_sloballoc_invariants(); \ + VAR = sloballoc(slob, (SIZE)); \ + g_check_cmp_ptr(VAR, ==, mem + (OFFSET)); \ + } while(0) + +#define check_sloballoc_fail(SIZE) do { \ + check_sloballoc_invariants(); \ + void *p = sloballoc(slob, (SIZE)); \ + g_check_cmp_ptr(p, ==, NULL); \ + } while(0) + +#define check_slobfree(P) do { \ + check_sloballoc_invariants(); \ + slobfree(slob, P); \ + } while(0) + +#define N 1024 + +#define SLOBALLOC_FIXTURE \ + static uint8_t mem[N] = {0x58}; \ + SLOB *slob = slobinit(mem, N); \ + size_t max = N - 2*sizeof(size_t) - sizeof(void *); \ + (void)max; /* silence warning */ \ + if(!slob) { \ + g_test_message("SLOB allocator init failed on line %d", __LINE__); \ + g_test_fail(); \ + } + +static void test_sloballoc_size(void) +{ + SLOBALLOC_FIXTURE + void *p; + + check_sloballoc(p, max, N-max); + check_slobfree(p); + + check_sloballoc_fail(N); + check_sloballoc_fail(max+1); + + check_sloballoc(p, max, N-max); + check_slobfree(p); + + check_sloballoc_invariants(); +} + +static void test_sloballoc_merge(void) +{ + SLOBALLOC_FIXTURE + void *p, *q, *r; + + check_sloballoc(p, 100, N-100); + check_slobfree(p); + check_sloballoc(p, max, N-max); + check_slobfree(p); + + check_sloballoc(p, 100, N-100); + check_sloballoc(q, 100, N-200-sizeof(size_t)); + check_slobfree(p); + check_sloballoc(p, 50, N-50); + check_sloballoc(r, 100, N-300-2*sizeof(size_t)); + check_slobfree(q); + check_sloballoc(q, 150, N-200-sizeof(size_t)); + check_slobfree(p); + check_slobfree(r); + check_slobfree(q); // merge left and right + + check_sloballoc_fail(max+1); + check_sloballoc(p, max, N-max); + check_slobfree(p); + + check_sloballoc_invariants(); +} + +static void test_sloballoc_small(void) +{ + SLOBALLOC_FIXTURE + void *p, *q, *r; + + check_sloballoc(p, 100, N-100); + check_sloballoc(q, 1, N-100-sizeof(size_t)-sizeof(void *)); + check_sloballoc(r, 100, N-200-2*sizeof(size_t)-sizeof(void *)); + check_slobfree(q); + check_sloballoc(q, 1, N-100-sizeof(size_t)-sizeof(void *)); + check_slobfree(p); + check_slobfree(r); + + check_sloballoc_invariants(); +} + +#define check_h_sloballoc(VAR, SIZE, OFFSET) do { \ + check_sloballoc_invariants(); \ + VAR = mm->alloc(mm, (SIZE)); \ + g_check_cmp_ptr(VAR, ==, mem + (OFFSET)); \ + } while(0) + +#define check_h_slobfree(P) do { \ + check_sloballoc_invariants(); \ + mm->free(mm, P); \ + } while(0) + +static void test_sloballoc_hammer(void) +{ + static uint8_t mem[N] = {0x58}; + HAllocator *mm = h_sloballoc(mem, N); int line = __LINE__; + SLOB *slob = ((void *)mm) + sizeof(HAllocator); + void *p, *q, *r; + + if(!mm) { + g_test_message("h_sloballoc() failed on line %d", line); + g_test_fail(); + } + + check_h_sloballoc(p, 100, N-100); + check_h_sloballoc(q, 1, N-100-sizeof(size_t)-sizeof(void *)); + check_h_sloballoc(r, 100, N-200-2*sizeof(size_t)-sizeof(void *)); + check_h_slobfree(q); + check_h_sloballoc(q, 1, N-100-sizeof(size_t)-sizeof(void *)); + check_h_slobfree(p); + check_h_slobfree(r); + + check_sloballoc_invariants(); +} + +#undef N + +void register_mm_tests(void) { + g_test_add_func("/core/mm/sloballoc/size", test_sloballoc_size); + g_test_add_func("/core/mm/sloballoc/merge", test_sloballoc_merge); + g_test_add_func("/core/mm/sloballoc/small", test_sloballoc_small); + g_test_add_func("/core/mm/sloballoc/hammer", test_sloballoc_hammer); +} + diff --git a/src/test_suite.c b/src/test_suite.c index cba18e8d..f5696442 100644 --- a/src/test_suite.c +++ b/src/test_suite.c @@ -24,6 +24,7 @@ extern void register_bitwriter_tests(); extern void register_parser_tests(); extern void register_grammar_tests(); extern void register_misc_tests(); +extern void register_mm_tests(); extern void register_benchmark_tests(); extern void register_regression_tests(); @@ -36,6 +37,7 @@ int main(int argc, char** argv) { register_parser_tests(); register_grammar_tests(); register_misc_tests(); + register_mm_tests(); register_regression_tests(); if (g_test_slow() || g_test_perf()) register_benchmark_tests(); diff --git a/src/test_suite.h b/src/test_suite.h index 83359f9e..ed640fd8 100644 --- a/src/test_suite.h +++ b/src/test_suite.h @@ -321,6 +321,7 @@ #define g_check_cmp_int64(n1, op, n2) g_check_inttype("%" PRId64, int64_t, n1, op, n2) #define g_check_cmp_uint32(n1, op, n2) g_check_inttype("%u", uint32_t, n1, op, n2) #define g_check_cmp_uint64(n1, op, n2) g_check_inttype("%" PRIu64, uint64_t, n1, op, n2) +#define g_check_cmp_ptr(n1, op, n2) g_check_inttype("%p", void *, n1, op, n2) #define g_check_cmpfloat(n1, op, n2) g_check_inttype("%g", float, n1, op, n2) #define g_check_cmpdouble(n1, op, n2) g_check_inttype("%g", double, n1, op, n2) -- GitLab