@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 $@)
#define INLINE_TABLE_LENGTH 64
/* TODO eliminate globals */
-sbBuffer g_hashtable_blocks = {0};
+sbPool g_hashtable_pool = {0};
typedef struct hashentry {
hV key;
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);
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 */
/* --- */
-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");
}
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;
}
/* 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;
};
} 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);
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) {
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) {
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);
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 */
}
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;
}
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, // <hashtable_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, // <hashtable_tombstone>
};
typedef struct hV {
#define __SB_MEM_H__
#include "global.h"
+
#include "arena.h"
#include "buffer.h"
#include "cstrutil.h"
+#include "pool.h"
#endif
--- /dev/null
+#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 ++;
+}
--- /dev/null
+#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