From: cassowarii Date: Tue, 30 Jun 2026 22:03:16 +0000 (-0700) Subject: factor out 'allocation pool' code from string + hash table X-Git-Url: https://git.cassowary.me/gitweb.cgi?a=commitdiff_plain;h=e7d5b701ab33e131fb1c305de4ec53e739f37571;p=sarabande.git factor out 'allocation pool' code from string + hash table --- diff --git a/Makefile b/Makefile index a58e1c1..85b4716 100644 --- a/Makefile +++ b/Makefile @@ -14,12 +14,14 @@ build/a.out: $(OBJECTS) @mkdir -p $(dir $@) $(CC) $(CFLAGS) -I$(SRC) $(OBJECTS) $(LIBS) -o $@ -.PHONY: parsedebug optimized +.PHONY: parsedebug optimized dev parsedebug: CFLAGS += -DPARSEDEBUG -parsedebug: build/a.out +parsedebug: clean build/a.out optimized: CFLAGS=-Wall -O3 -flto -optimized: build/a.out +optimized: clean build/a.out + +dev: clean build/a.out $(OBJ)/%.o: $(SRC)/%.c @mkdir -p $(dir $@) diff --git a/src/data/hashtable.c b/src/data/hashtable.c index 6cef9a3..355e119 100644 --- a/src/data/hashtable.c +++ b/src/data/hashtable.c @@ -6,7 +6,7 @@ #define INLINE_TABLE_LENGTH 64 /* TODO eliminate globals */ -sbBuffer g_hashtable_blocks = {0}; +sbPool g_hashtable_pool = {0}; typedef struct hashentry { hV key; @@ -20,19 +20,12 @@ typedef struct hashtbl { flag allocated; flag is_inline; union { + /* TODO should be SoA instead of AoS */ hashentry inline_entries[INLINE_TABLE_LENGTH]; hashentry *external_entries; }; } hashtbl; -typedef struct hashblk { - usize id; - usize used_count; - usize last_index; - hashtbl tables[HASHES_PER_BLOCK]; -} hashblk; - -static hashblk *alloc_new_block(); static hashtbl *new_tbl(usize initial_size); static hashentry *get_entry_ptr_for_tbl(hashtbl *t, usize *length_out); static hashtbl *find_tbl_for_handle(hHash handle); @@ -43,12 +36,11 @@ static hashentry *set_key_in_array(hashentry *entries, usize length, hV *key, hV static hashentry *find_entry_by_key(hashtbl *t, hV *key); void sbHash_sys_init() { - sbBuffer_initialize(&g_hashtable_blocks, 8 * sizeof(hashblk*)); - alloc_new_block(); + sbPool_initialize(&g_hashtable_pool, sizeof(hashtbl), HASHES_PER_BLOCK); } void sbHash_sys_deinit() { - //sbBuffer_deinitialize(&g_hashtable_blocks); + //sbPool_deinitialize(&g_hashtable_pool); } /* https://en.wikipedia.org/wiki/Fowler%E2%80%93Noll%E2%80%93Vo_hash_function */ @@ -113,54 +105,6 @@ void sbHash_delete(hHash h, hV *key, hV *value) { /* --- */ -static hashblk *alloc_new_block() { - usize nblocks = g_hashtable_blocks.size / sizeof(hashblk*); - hashblk *new_block = calloc(1, sizeof(hashblk)); - new_block->id = nblocks; - sbBuffer_append(&g_hashtable_blocks, &new_block, sizeof(hashblk*)); - return new_block; -} - -static hashblk *find_free_block() { - usize nblocks = g_hashtable_blocks.size / sizeof(hashblk*); - hashblk *free_block = NULL; - for (usize i = 0; i < nblocks; i++) { - if (((hashblk**)g_hashtable_blocks.data)[i]->used_count < HASHES_PER_BLOCK) { - free_block = ((hashblk**)g_hashtable_blocks.data)[i]; - break; - } - } - - if (free_block == NULL) { - free_block = alloc_new_block(); - } - - return free_block; -} - -static hashblk *get_block(usize index) { - usize nblocks = g_hashtable_blocks.size / sizeof(hashblk*); - if (index > nblocks) PANIC("request for a hash block (#%zu) that does not exist!", index); - return ((hashblk**)g_hashtable_blocks.data)[index]; -} - -static hashtbl *find_free_entry(hashblk *blk) { - if (blk->used_count >= HASHES_PER_BLOCK) PANIC("request for new table in full hashblk!"); - - while (blk->tables[blk->last_index].allocated) { - blk->last_index++; - blk->last_index %= HASHES_PER_BLOCK; - } - blk->tables[blk->last_index].allocated = 1; - blk->used_count ++; - - hashtbl *result = &blk->tables[blk->last_index]; - - result->handle = blk->id * HASHES_PER_BLOCK + blk->last_index; - - return result; -} - static void set_hashtbl_size(hashtbl *t, usize new_size, flag rehash_all) { if (new_size < INLINE_TABLE_LENGTH) new_size = INLINE_TABLE_LENGTH; if (new_size < t->size) PANIC("cannot shrink hashtable allocation"); @@ -274,12 +218,13 @@ static hashentry delete_key(hashtbl *t, hV *key) { } static hashtbl *find_tbl_for_handle(hHash handle) { - hashblk *block = get_block(handle / HASHES_PER_BLOCK); - return &block->tables[handle % HASHES_PER_BLOCK]; + return sbPool_get_entry(&g_hashtable_pool, handle); } static hashtbl *new_tbl(usize initial_size) { - hashtbl *t = find_free_entry(find_free_block()); + usize index; + hashtbl *t = sbPool_alloc(&g_hashtable_pool, &index); set_hashtbl_size(t, initial_size, FALSE); + t->handle = index; return t; } diff --git a/src/data/string.c b/src/data/string.c index db710d7..38796df 100644 --- a/src/data/string.c +++ b/src/data/string.c @@ -25,15 +25,7 @@ /* TODO: Move these globals into some kind of "execution context" struct * that we can pass around to be reentrant */ -/* also TODO a better (slightly more annoying to code) way to structure - * this would be SoA style: instead of storing used_count and allocated - * inside the block and entry respectively, store them in a separate - * array next to the pointers that we can scan quickly. less important - * for allocated since it's already in the strblk anyway, but for used_count - * this could be important because the counts will otherwise be scattered - * to the four winds */ -sbBuffer g_string_blocks; -usize g_free_block_index; +sbPool g_string_pool; typedef struct strentry { GCINFO gc; @@ -47,18 +39,9 @@ typedef struct strentry { }; } strentry; -typedef struct strblk { - usize id; - usize used_count; - usize next_free_index; - strentry entries[STRINGS_PER_BLOCK]; -} strblk; - static const char *get_ptr_of_entry(strentry *e); static char *get_mutable_ptr_of_entry(strentry *e, usize length, hString *handle); static strentry *find_entry_for_handle(hString handle); -static strblk *get_strblk(usize index); -static strentry *get_entry(strblk *blk, usize index); static strentry *new_entry(usize length); static void place_str_at_offset(strentry *e, usize offset, const char *str, usize length); static int buffers_eq(const char *a_ptr, usize a_length, const char *b_ptr, usize b_length); @@ -67,22 +50,17 @@ static flag is_tinystr(hString handle); static usize tinystr_length(hString handle); static void tinystr_into_buffer(char *buffer, hString handle, usize max_length); static hString tinystr_from_buffer(const char *buffer, usize length); -static strblk *new_strblk(); static void set_entry_size(strentry *e, usize length); void sbString_sys_init() { - g_free_block_index = 0; - sbBuffer_initialize(&g_string_blocks, sizeof(strblk*) * 8); - - /* we need to have at least one strblk to start */ - new_strblk(); + sbPool_initialize(&g_string_pool, sizeof(strentry), STRINGS_PER_BLOCK); } void sbString_sys_deinit() { /* TODO free these i guess, or don't deinitialize this buffer, right now this * would just drop the pointers to no benefit. look, i'm going to write a * garbage collector, ok? give me a second */ - //sbBuffer_deinitialize(&g_string_blocks); + //sbPool_deinitialize(&g_string_pool); } hString sbString_new(const char *value, usize length) { @@ -265,8 +243,7 @@ static usize tinystr_length(hString handle) { static strentry *find_entry_for_handle(hString handle) { if (is_tinystr(handle)) return NULL; - strblk *blk = get_strblk((handle & ~FLAG_NONTINY) / STRINGS_PER_BLOCK); - return get_entry(blk, (handle & ~FLAG_NONTINY) % STRINGS_PER_BLOCK); + return sbPool_get_entry(&g_string_pool, handle & ~FLAG_NONTINY); } static strentry *duplicate_strentry(strentry *e, usize length) { @@ -381,77 +358,6 @@ static hString tinystr_from_buffer(const char *buffer, usize length) { return new_handle; } -static strblk *get_strblk(usize index) { - usize nblocks = g_string_blocks.size / sizeof(strblk*); - if (index >= nblocks) PANIC("attempt to get a string from a block (#%zu) that does not exist!", index); - - return ((strblk**)g_string_blocks.data)[index]; -} - -static strentry *get_entry(strblk *blk, usize index) { - if (index >= STRINGS_PER_BLOCK) PANIC("attempt to get a string (#%zu) that does not exist in block (#%zu)!", index, blk->id); - - return &blk->entries[index]; -} - -static strblk *new_strblk() { - usize nblocks = g_string_blocks.size / sizeof(strblk*); - strblk *new_block = calloc(1, sizeof(strblk)); - new_block->id = nblocks; - strblk **where_to_put = sbBuffer_expand(&g_string_blocks, sizeof(strblk*)); - *where_to_put = new_block; - return new_block; -} - -static strentry *alloc_entry(strblk *blk) { - if (blk->used_count == STRINGS_PER_BLOCK) PANIC("attempt to allocate from a full strblk (#%zu)!", blk->id); - - blk->used_count ++; - - usize free_index = blk->next_free_index; - - if (blk->used_count < STRINGS_PER_BLOCK) { - usize next_free_index = blk->next_free_index; - do { - next_free_index ++; - next_free_index %= STRINGS_PER_BLOCK; - } while (blk->entries[next_free_index].allocated && next_free_index != blk->next_free_index); - - if (next_free_index == blk->next_free_index) { - PANIC("free count lied! for strblk (#%zu)", blk->id); - } - - blk->next_free_index = next_free_index; - } - - blk->entries[free_index].allocated = TRUE; - - return &blk->entries[free_index]; -} - -static strblk *find_free_block() { - strblk *block_with_free = get_strblk(g_free_block_index); - - if (block_with_free->used_count == STRINGS_PER_BLOCK) { - usize nblocks = g_string_blocks.size / sizeof(strblk*); - usize start_index = g_free_block_index; - - do { - g_free_block_index ++; - g_free_block_index %= nblocks; - block_with_free = get_strblk(g_free_block_index); - } while (g_free_block_index != start_index && block_with_free->used_count == STRINGS_PER_BLOCK); - - if (g_free_block_index == start_index) { - /* Where did that bring you? Back to me. */ - block_with_free = new_strblk(); - g_free_block_index = nblocks; - } - } - - return block_with_free; -} - static void set_entry_size(strentry *e, usize length) { if (e->is_external) { sbBuffer_set_size(&e->somewhere_else_value, length + 1); @@ -460,7 +366,7 @@ static void set_entry_size(strentry *e, usize length) { e->somewhere_else_value.data[length] = '\0'; } else { if (length > INLINE_BUFFER_SIZE - 1) { - /* new length is too big for inline, so we need to copy to the heap */ + /* new length is too big for inline, so we need to copy to the external heap */ sbBuffer new_buf = {0}; /* length + 1 for NUL terminator. even though we track the length, it's * easier to just preserve NUL terminator for talking to C lib functions */ @@ -499,9 +405,9 @@ static void place_str_at_offset(strentry *e, usize offset, const char *str, usiz } static strentry *new_entry(usize length) { - strblk *new_block = find_free_block(); - strentry *new_entry = alloc_entry(new_block); - new_entry->handle = ((new_block->id * STRINGS_PER_BLOCK + (new_entry - new_block->entries)) | FLAG_NONTINY); + usize index; + strentry *new_entry = sbPool_alloc(&g_string_pool, &index); + new_entry->handle = (index | FLAG_NONTINY); set_entry_size(new_entry, length); return new_entry; } diff --git a/src/data/value.h b/src/data/value.h index 439e060..5ab763f 100644 --- a/src/data/value.h +++ b/src/data/value.h @@ -15,19 +15,19 @@ typedef u64 hSymbol; typedef i64 hInteger; enum intrinsic_type { - IT_NOTHING, // sentinel for "no value here" - IT_NIL, // nil ("there is a value here, but it's nil") - IT_BOOLEAN, // true / false - IT_STRING, // `abcdefg` - IT_SYMBOL, // :hello - IT_INTEGER, // 324892 - IT_FLOAT, // 0.0345 - IT_DATETIME, // unix timestamp - IT_REF, // pointer \abc - IT_LIST, // list [1, 3, 5, 7] - IT_HASH, // hash {a: 1, b: 2} - IT_FUNCTION, // function => a, b { a + b } - ITX_TOMBSTONE, // + IT_NOTHING, // sentinel for "no value here" + IT_NIL = -1, // nil ("there is a value here, but it's nil") + IT_BOOLEAN = -2, // true / false + IT_STRING = -3, // `abcdefg` + IT_SYMBOL = -4, // :hello + IT_INTEGER = -5, // 324892 + IT_FLOAT = -6, // 0.0345 + IT_DATETIME = -7, // unix timestamp + IT_REF = -8, // pointer \abc + IT_LIST = -9, // list [1, 3, 5, 7] + IT_HASH = -10, // hash {a: 1, b: 2} + IT_FUNCTION = -11, // function => a, b { a + b } + ITX_TOMBSTONE = -12, // }; typedef struct hV { diff --git a/src/mem/mem.h b/src/mem/mem.h index 1498f62..a37e553 100644 --- a/src/mem/mem.h +++ b/src/mem/mem.h @@ -2,8 +2,10 @@ #define __SB_MEM_H__ #include "global.h" + #include "arena.h" #include "buffer.h" #include "cstrutil.h" +#include "pool.h" #endif diff --git a/src/mem/pool.c b/src/mem/pool.c new file mode 100644 index 0000000..515fc38 --- /dev/null +++ b/src/mem/pool.c @@ -0,0 +1,102 @@ +#include "mem/pool.h" + +/* this could be faster: computing offsets at runtime isn't + * strictly speaking optimal. but this would be insane to macro. + * "if only c had templates!" <- sentences dreamed up by the + * utterly deranged. maybe i should manually code-generate these + * later. */ + +// sorry +// BLOCK_ALLOCATION_FLAGS(pool, block_index) gives the array of flags of length `block_size` for the given block +// BLOCK_ALLOCATED_DATA(pool, block_index) gives a pointer to the start of the actual slot array (of size `elem_size * block_size`) +#define BLOCK_ALLOCATION_FLAGS(pool, blk) ((flag*)(((Block*)(pool->block_ptrs[blk]))->data)) +#define BLOCK_ALLOCATED_DATA(pool, blk) ((char*)(&((Block*)(pool->block_ptrs[blk]))->data[pool->block_size])) + +/* +typedef struct sbPool { + usize elem_size; + usize block_size; + usize num_blocks; + usize block_ptr_capacity; + usize next_block_id; + void **block_ptrs; + u16 *used_counts; +} sbPool; +*/ + +typedef struct Block { + usize last_index; + char data[]; +} Block; + +void alloc_new_block(hPool pl); + +void sbPool_initialize(hPool pl, usize elem_size, usize block_size) { + *pl = (sbPool) {0}; + pl->elem_size = elem_size; + pl->block_size = block_size; + alloc_new_block(pl); +} + +void sbPool_deinitialize(hPool pl) { + for (usize i = 0; i < pl->num_blocks; i++) { + free(pl->block_ptrs[i]); + } + *pl = (sbPool) {0}; +} + +void *sbPool_alloc(hPool pl, usize *id_out) { + usize start_index = pl->next_block_id; + /* if current block is full, look for one that's empty */ + while (pl->used_counts[pl->next_block_id] == pl->block_size) { + pl->next_block_id ++; + pl->next_block_id %= pl->num_blocks; + if (pl->next_block_id == start_index) { + /* Where did that bring you? Back to me. */ + pl->next_block_id = pl->num_blocks; + alloc_new_block(pl); + break; + } + } + + Block *block = pl->block_ptrs[pl->next_block_id]; + while (BLOCK_ALLOCATION_FLAGS(pl, pl->next_block_id)[block->last_index]) { + block->last_index ++; + block->last_index %= pl->block_size; + } + BLOCK_ALLOCATION_FLAGS(pl, pl->next_block_id)[block->last_index] = 1; + pl->used_counts[pl->next_block_id] ++; + + if (id_out) *id_out = pl->next_block_id * pl->block_size + block->last_index; + + return &BLOCK_ALLOCATED_DATA(pl, pl->next_block_id)[block->last_index * pl->elem_size]; +} + +void *sbPool_get_entry(hPool pl, usize index) { + usize block_id = index / pl->block_size; + usize elem_id = index % pl->block_size; + return &BLOCK_ALLOCATED_DATA(pl, block_id)[elem_id * pl->elem_size]; +} + +/* this 'alloc' function gives us an unallocated slot. there is no 'free' on individual items + * (we only have a pointer to the individual item anyway), but items should be reclaimed + * by the garbage collector. once we write the garbage collector, at least...) */ + +/* --- */ + +void alloc_new_block(hPool pl) { + /* allocate block header + 'block_size' bytes for tracking allocation + enough space + * to hold all the elements */ + void *new_block = calloc(1, sizeof(Block) + pl->block_size + pl->elem_size * pl->block_size); + if (pl->num_blocks >= pl->block_ptr_capacity) { + usize new_capacity = pl->block_ptr_capacity * 2; + void **new_data = realloc(pl->block_ptrs, new_capacity); + if (!new_data) { + PANIC("This should probably trigger the garbage collector or something!"); + } + pl->block_ptrs = new_data; + } + + pl->block_ptrs[pl->num_blocks] = new_block; + pl->num_blocks ++; +} diff --git a/src/mem/pool.h b/src/mem/pool.h new file mode 100644 index 0000000..321dffd --- /dev/null +++ b/src/mem/pool.h @@ -0,0 +1,26 @@ +#ifndef __SARABANDE_POOL_H__ +#define __SARABANDE_POOL_H__ + +#include "common.h" + +typedef struct sbPool { + usize elem_size; + usize block_size; + usize num_blocks; + usize block_ptr_capacity; + usize next_block_id; + void **block_ptrs; + u16 *used_counts; +} sbPool; + +typedef sbPool *hPool; + +void sbPool_initialize(hPool pl, usize elem_size, usize block_size); + +void sbPool_deinitialize(hPool pl); + +void *sbPool_alloc(hPool pl, usize *id_out); + +void *sbPool_get_entry(hPool pl, usize index); + +#endif