From 30cc709b8cdbeaf4a83088eea16262f646ad15f4 Mon Sep 17 00:00:00 2001 From: "Sven M. Hallberg" Date: Sun, 27 Dec 2015 14:45:21 +0100 Subject: [PATCH 1/5] 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 7a1b9d4..3d25452 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 1be297c..6c2bf49 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 0000000..8b6cbc5 --- /dev/null +++ b/src/sloballoc.c @@ -0,0 +1,216 @@ +// first-fit SLOB (simple list of blocks) allocator + +#include "sloballoc.h" +#include +#include + +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 0000000..ecdc479 --- /dev/null +++ b/src/sloballoc.h @@ -0,0 +1,15 @@ +#ifndef SLOBALLOC_H_SEEN +#define SLOBALLOC_H_SEEN + +#include + +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 0000000..620d4e8 --- /dev/null +++ b/src/t_mm.c @@ -0,0 +1,148 @@ +#include +#include +#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 cba18e8..f569644 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 83359f9..ed640fd 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) From d9c4492fd9581ef5b804269f0e48a628173f22c4 Mon Sep 17 00:00:00 2001 From: "Sven M. Hallberg" Date: Tue, 6 Dec 2016 18:21:59 +0100 Subject: [PATCH 2/5] silence gcc about "possible" (impossible) use of uninitialized variable --- src/sloballoc.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sloballoc.c b/src/sloballoc.c index 8b6cbc5..d42f50f 100644 --- a/src/sloballoc.c +++ b/src/sloballoc.c @@ -71,7 +71,7 @@ void *sloballoc(SLOB *slob, size_t size) void slobfree(SLOB *slob, void *a_) { struct alloc *a = a_ - sizeof(struct alloc); - struct block *b, **p, *left=NULL, *right=NULL, **rightp; + struct block *b, **p, *left=NULL, *right=NULL, **rightp=NULL; // sanity check: a lies inside slob assert((void *)a >= (void *)slob->data); From fedb36ed897ac5be625ce17898e69e725507d676 Mon Sep 17 00:00:00 2001 From: "Sven M. Hallberg" Date: Wed, 7 Dec 2016 12:00:27 +0100 Subject: [PATCH 3/5] avoid arithmetic on void pointers to be more standard-conforming --- src/sloballoc.c | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/src/sloballoc.c b/src/sloballoc.c index d42f50f..24537ff 100644 --- a/src/sloballoc.c +++ b/src/sloballoc.c @@ -55,7 +55,7 @@ void *sloballoc(SLOB *slob, size_t size) 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; + struct alloc *a = (struct alloc *)(b->alloc.data + b->alloc.size); a->size = size; return a->data; } else if(b->alloc.size >= size) { @@ -74,16 +74,16 @@ void slobfree(SLOB *slob, void *a_) struct block *b, **p, *left=NULL, *right=NULL, **rightp=NULL; // sanity check: a lies inside slob - assert((void *)a >= (void *)slob->data); - assert((void *)a->data + a->size <= (void *)slob->data + slob->size); + assert((uint8_t *)a >= slob->data); + assert(a->data + a->size <= 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) { + if((uint8_t *)a == b->alloc.data + b->alloc.size) { assert(!left); left = b; } - if((void *)a->data + a->size == b) { + if(a->data + a->size == (uint8_t *)b) { assert(!right); right = b; rightp = p; @@ -121,7 +121,7 @@ int slobcheck(SLOB *slob) // 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; + uint8_t *p; size_t nblocks=0, nfree=0; #define FORBLOCKS \ @@ -131,13 +131,13 @@ int slobcheck(SLOB *slob) // 1. memory area is divided seamlessly and exactly into n blocks FORBLOCKS { - if(p < (void *)slob->data) + if(p < slob->data) return 1; - if(p > (void *)slob->data + slob->size) + if(p > slob->data + slob->size) return 2; nblocks++; - struct alloc *a = p; + struct alloc *a = (struct alloc *)p; if(a->size > UINTPTR_MAX - (uintptr_t)p) return 3; @@ -154,7 +154,7 @@ int slobcheck(SLOB *slob) // 4. every element of the free list is one of the valid blocks. FORBLOCKS - if(p == b) break; + if(p == (uint8_t *)b) break; if(!p) return 6; } @@ -163,7 +163,7 @@ int slobcheck(SLOB *slob) FORBLOCKS { size_t count=0; for(struct block *b=slob->head; b; b=b->next) - if(p == b) count++; + if(p == (uint8_t *)b) count++; if(count > 1) return 7; } From ec8249513c0c5da126972e737ba3ba3dd600e413 Mon Sep 17 00:00:00 2001 From: "Sven M. Hallberg" Date: Wed, 7 Dec 2016 12:06:53 +0100 Subject: [PATCH 4/5] avoid more void pointer arithmetic --- src/sloballoc.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/sloballoc.c b/src/sloballoc.c index 24537ff..d16a879 100644 --- a/src/sloballoc.c +++ b/src/sloballoc.c @@ -30,7 +30,7 @@ SLOB *slobinit(void *mem, size_t size) slob = mem; slob->size = size - sizeof(SLOB); - slob->head = mem + sizeof(SLOB); + slob->head = (struct block *)((uint8_t *)mem + sizeof(SLOB)); slob->head->alloc.size = slob->size - sizeof(struct alloc); slob->head->next = NULL; @@ -70,7 +70,7 @@ void *sloballoc(SLOB *slob, size_t size) void slobfree(SLOB *slob, void *a_) { - struct alloc *a = a_ - sizeof(struct alloc); + struct alloc *a = (struct alloc *)((uint8_t *)a_ - sizeof(struct alloc)); struct block *b, **p, *left=NULL, *right=NULL, **rightp=NULL; // sanity check: a lies inside slob @@ -203,7 +203,7 @@ HAllocator *h_sloballoc(void *mem, size_t size) return NULL; HAllocator *mm = mem; - SLOB *slob = slobinit(mem + sizeof(HAllocator), size - sizeof(HAllocator)); + SLOB *slob = slobinit((uint8_t *)mem + sizeof(HAllocator), size - sizeof(HAllocator)); if(!slob) return NULL; assert(slob == (SLOB *)(mm+1)); From 631ca40f9170b10491db442ff3395b8f26cff4c3 Mon Sep 17 00:00:00 2001 From: "Sven M. Hallberg" Date: Wed, 7 Dec 2016 12:39:49 +0100 Subject: [PATCH 5/5] avoid embedding data[] in struct block to satisfy windows build --- src/sloballoc.c | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/src/sloballoc.c b/src/sloballoc.c index d16a879..e164cab 100644 --- a/src/sloballoc.c +++ b/src/sloballoc.c @@ -10,7 +10,7 @@ struct alloc { }; struct block { - struct alloc alloc; + size_t size; // read: struct alloc struct block *next; }; @@ -31,7 +31,7 @@ SLOB *slobinit(void *mem, size_t size) slob = mem; slob->size = size - sizeof(SLOB); slob->head = (struct block *)((uint8_t *)mem + sizeof(SLOB)); - slob->head->alloc.size = slob->size - sizeof(struct alloc); + slob->head->size = slob->size - sizeof(struct alloc); slob->head->next = NULL; return slob; @@ -52,16 +52,16 @@ void *sloballoc(SLOB *slob, size_t size) // scan list for the first block of sufficient size for(p=&slob->head; (b=*p); p=&b->next) { - if(b->alloc.size >= remblock) { + if(b->size >= remblock) { // cut from the end of the block - b->alloc.size -= sizeof(struct alloc) + size; - struct alloc *a = (struct alloc *)(b->alloc.data + b->alloc.size); + b->size -= sizeof(struct alloc) + size; + struct alloc *a = (struct alloc *)(((struct alloc *)b)->data + b->size); a->size = size; return a->data; - } else if(b->alloc.size >= size) { + } else if(b->size >= size) { // when a block fills, it converts directly to a struct alloc *p = b->next; // unlink - return b->alloc.data; + return ((struct alloc *)b)->data; } } @@ -79,7 +79,7 @@ void slobfree(SLOB *slob, void *a_) // scan list for blocks adjacent to a for(p=&slob->head; (b=*p); p=&b->next) { - if((uint8_t *)a == b->alloc.data + b->alloc.size) { + if((uint8_t *)a == ((struct alloc *)b)->data + b->size) { assert(!left); left = b; } @@ -91,8 +91,8 @@ void slobfree(SLOB *slob, void *a_) if(left && right) { // extend left and unlink right - left->alloc.size += sizeof(*a) + a->size + - sizeof(right->alloc) + right->alloc.size; + left->size += sizeof(*a) + a->size + + sizeof(struct alloc) + right->size; *rightp = right->next; return; } @@ -100,10 +100,10 @@ void slobfree(SLOB *slob, void *a_) if(left) { // extend left to absorb a - left->alloc.size += sizeof(*a) + a->size; + left->size += sizeof(*a) + a->size; } else if(right) { // shift and extend right to absorb a - right->alloc.size += sizeof(*a) + a->size; + right->size += sizeof(*a) + a->size; *rightp = (struct block *)a; **rightp = *right; } else { // spawn new block over a