diff --git a/cig.h b/cig.h index ee5cf68..f9ac861 100644 --- a/cig.h +++ b/cig.h @@ -138,7 +138,7 @@ allocator_t allocator_from_arena(arena_allocator_t *this); // dynamic arrays ////////////////////////////////////////////////////////////// typedef struct dyn_array_header { - size_t n_items, capacity, itemsize; + size_t n_items, capacity, item_size; allocator_t allocator; union { // allocates a little more memory than needed, but who cares? @@ -149,7 +149,7 @@ typedef struct dyn_array_header { typedef struct dyn_array_create_func_args { allocator_t allocator; - size_t itemsize; + size_t item_size; size_t initial_capacity; const char *file; int line; @@ -157,7 +157,7 @@ typedef struct dyn_array_create_func_args { void *dyn_array_create_func(dyn_array_create_func_args_t args); #define make_arr(TYPE, ...) ((TYPE *)dyn_array_create_func((dyn_array_create_func_args_t){ \ - .itemsize = sizeof(TYPE), \ + .item_size = sizeof(TYPE), \ .file = __FILE__, \ .line = __LINE__, \ .allocator = __VA_ARGS__, \ @@ -165,7 +165,7 @@ void *dyn_array_create_func(dyn_array_create_func_args_t args); #define init_arr(PTR, ...) do { \ PTR = dyn_array_create_func((dyn_array_create_func_args_t){ \ - .itemsize = sizeof(*PTR), \ + .item_size = sizeof(*PTR), \ .file = __FILE__, \ .line = __LINE__, \ .allocator = __VA_ARGS__, \ @@ -239,11 +239,12 @@ typedef unsigned int (*hash_func_t)(void *key); typedef bool (*equals_func_t)(void *key1, void *key2); typedef struct map_header { - int capacity; - int itemsize; - int keysize; - int mapping_capacity; - int n_items; + unsigned int capacity; + unsigned int item_size; + unsigned int key_size; + unsigned int mapping_capacity; + unsigned int n_items; + unsigned int key_offset; allocator_t allocator; hash_func_t hash; equals_func_t equals; @@ -259,9 +260,10 @@ typedef struct map_create_func_args { const char *file; hash_func_t hash; // OPTIONAL equals_func_t equals; // OPTIONAL - int initial_capacity; // OPTIONAL - int itemsize; - int keysize; + unsigned int initial_capacity; // OPTIONAL + unsigned int item_size; + unsigned int key_offset; + unsigned int key_size; int line; } map_create_func_args_t; @@ -271,16 +273,24 @@ void *map_create_func(map_create_func_args_t args); #define init_map(PTR, ...) do { \ STATIC_ASSERT(((size_t)PTR) == ((size_t)(&PTR->key))); \ PTR = map_create_func((map_create_func_args_t) { \ - .itemsize=sizeof(*PTR), \ - .keysize=sizeof(PTR->key), \ + .item_size=sizeof(*PTR), \ + .key_size=sizeof(PTR->key), \ .file=__FILE__, \ .line=__LINE__, \ .allocator=__VA_ARGS__ \ + .key_offset=offsetof(PTR->key), \ }); \ } while(0); int map_len(void *this); int map_cap(void *this); +uint32_t fnv_1a(const uint8_t *bytes, const unsigned int size); +/* + * Hashes modulo the header->mapping capacity. Will keep hashing the hash until + * a free or gravestone slot is found. Does not write to the mapping array, only + * returns the next valid index to overwrite. Uses default or provided hashing and equals functions + */ +unsigned int map_pair_hash(void *this, void *pair); // CLI ///////////////////////////////////////////////////////////////////////// diff --git a/dyn_array.c b/dyn_array.c index 50e63d3..5965aa4 100644 --- a/dyn_array.c +++ b/dyn_array.c @@ -5,13 +5,13 @@ void *dyn_array_create_func(dyn_array_create_func_args_t args) { dyn_array_header_t *header = allocator_alloc_func( args.allocator, - sizeof(dyn_array_header_t) + args.itemsize * args.initial_capacity, + sizeof(dyn_array_header_t) + args.item_size * args.initial_capacity, args.file, args.line ); header->n_items = 0; header->capacity = args.initial_capacity; - header->itemsize = args.itemsize; + header->item_size = args.item_size; header->allocator = args.allocator; return &header->bytes; } @@ -29,7 +29,7 @@ void *dyn_array_grow_func(void *this, size_t n_new_items, const char *file, int header = allocator_resize_func( header->allocator, header, - sizeof(dyn_array_header_t) + header->itemsize * new_capacity, + sizeof(dyn_array_header_t) + header->item_size * new_capacity, file, line ); @@ -71,8 +71,8 @@ bool dyn_array_contains_func(void *this, uint8_t *value) { dyn_array_header_t *header = PTR_FROM_FIELD_PTR(dyn_array_header_t, bytes, this); for (size_t i = 0; i < header->n_items; i++) { bool is_equal = true; - for (size_t off = 0; off < header->itemsize; off++) { - if (header->bytes[i*header->itemsize+off] != value[off]) { + for (size_t off = 0; off < header->item_size; off++) { + if (header->bytes[i*header->item_size+off] != value[off]) { is_equal = false; break; } @@ -86,10 +86,10 @@ bool dyn_array_contains_func(void *this, uint8_t *value) { bool dyn_array_contains_eq_func(void *this, uint8_t *value, dyn_array_eq_fn eq) { dyn_array_header_t *header = PTR_FROM_FIELD_PTR(dyn_array_header_t, bytes, this); - size_t itemsize = header->itemsize; + size_t item_size = header->item_size; for (size_t i = 0; i < header->n_items; i++) { - void *element = &header->bytes[i * itemsize]; + void *element = &header->bytes[i * item_size]; if (eq(element, value)) { return true; } @@ -116,5 +116,5 @@ void dyn_array_bounds_check_func(void *this, size_t index, const char *file, int void arr_qsort(void *this, dyn_array_cmp_fn cmp_fn) { dyn_array_header_t *header = PTR_FROM_FIELD_PTR(dyn_array_header_t, bytes, this); - qsort(this, header->n_items, header->itemsize, cmp_fn); + qsort(this, header->n_items, header->item_size, cmp_fn); } diff --git a/map.c b/map.c index 414779d..85a458e 100644 --- a/map.c +++ b/map.c @@ -1,29 +1,29 @@ #include "cig.h" #include -#define FREE (-1) -#define GRAVESTONE (-2) +#define FLAG_FREE (-1) +#define FLAG_GRAVESTONE (-2) -static inline int mapping_cap(int capacity) { +static inline unsigned int mapping_cap(unsigned int capacity) { return capacity * 2; } typedef struct internal_map_sizes { int bytes; - int cap_of_items_arr; + int real_cap_of_items_arr; } internal_map_sizes_t; -internal_map_sizes_t internal_map_sizes(int capacity, int itemsize) { +internal_map_sizes_t internal_map_sizes(unsigned int capacity, unsigned int item_size) { const size_t cap_of_index_arr = sizeof(int) * mapping_cap(capacity); const size_t INC = ALIGN_OF(int); - const size_t SIZE = itemsize * capacity; + const size_t SIZE = item_size * capacity; const size_t cap_of_items_arr = (SIZE + INC - 1) / INC; const size_t bytes = sizeof(map_header_t) + cap_of_items_arr + cap_of_index_arr; return (internal_map_sizes_t){ .bytes=bytes, - .cap_of_items_arr=cap_of_items_arr, + .real_cap_of_items_arr=cap_of_items_arr, }; } @@ -32,7 +32,7 @@ void *map_create_func( ) { internal_map_sizes_t sizes = internal_map_sizes( args.initial_capacity, - args.itemsize + args.item_size ); map_header_t *header = allocator_alloc_func( @@ -44,16 +44,16 @@ void *map_create_func( header->n_items = 0; header->capacity = args.initial_capacity; - header->itemsize = args.itemsize; - header->keysize = args.keysize; + header->item_size = args.item_size; + header->key_size = args.key_size; header->allocator = args.allocator; header->hash = args.hash; header->equals = args.equals; - header->mapping_arr = (int*) &header->bytes[sizes.cap_of_items_arr]; + header->mapping_arr = (int*) &header->bytes[sizes.real_cap_of_items_arr]; header->mapping_capacity = mapping_cap(args.initial_capacity); for ( int i = 0; i < header->mapping_capacity; i++ ) { - header->mapping_arr[i] = -1; + header->mapping_arr[i] = FLAG_FREE; } return header->bytes; @@ -61,30 +61,42 @@ void *map_create_func( void *map_grow_func(void *this, const char *file, int line) { map_header_t *header = PTR_FROM_FIELD_PTR(map_header_t, bytes, this); - int new_size = header->n_items + 1; + int new_n_items = header->n_items + 1; - if (header->capacity < new_size) { + if (header->capacity < new_n_items) { allocator_t allocator = header->allocator; - int new_capacity = 1; + unsigned int new_capacity = 1; if (header->capacity > 0) { new_capacity = header->capacity * 2; } internal_map_sizes_t new_size = internal_map_sizes( new_capacity, - header->itemsize + header->item_size ); header = allocator_resize_func(allocator, header, new_size.bytes, file, line); + header->mapping_arr = (int*) &header->bytes[new_size.real_cap_of_items_arr]; + header->mapping_capacity = mapping_cap(new_capacity); + header->capacity=new_capacity; - static_assert(0, "Overwrite the mapping arr with -1, then iterate" - "through the items arr and find the hashes of the values to populate the" - "mappings"); - - static_assert(0, "make sure the mapping_arr field is set to the new and" - "correct pointer!"); - + // first we have to clear the entire mapping array + for ( unsigned int i = 0; i < header->mapping_capacity; i++ ) { + header->mapping_arr[i] = FLAG_FREE; + } + + // Then we have to re-hash and map all the items in the existing map. + // The reason for re-hashing is that all hashes are moduloed by the + // size of the mapping array, so the resulting hash will likely change. + for ( unsigned int i = 0; i < header->n_items; i++ ) { + void *pair = &this[i*header->item_size]; + // TODO: make the map_pair_hash more private? would be nice to just + // pass in the header, and I don't really think it is needed + // directly by the user of the library. + unsigned int mapping_index = map_pair_hash(this, pair); + header->mapping_arr[mapping_index] = i; + } } - header->n_items = new_size; + header->n_items = new_n_items; return header->bytes; } @@ -103,12 +115,12 @@ void map_shrink_func(void *this, const char *file, int line) { static_assert(0, "Hash the key at the removed item to find it in the mapping array, and set it to the gravestone value."); } -static inline uint32_t fnv_1a(const uint8_t *bytes, const int length) { +uint32_t fnv_1a(const uint8_t *bytes, const unsigned int size) { // Yanked from: https://en.wikipedia.org/wiki/Fowler%E2%80%93Noll%E2%80%93Vo_hash_function const uint32_t PRIME = 0x01000193; const uint32_t OFFSET = 0x811c9dc5; uint32_t hash = OFFSET; - for (int i = 0; i < length; i++) { + for (int i = 0; i < size; i++) { uint32_t byte = (uint32_t)bytes[i]; hash = hash ^ byte; hash = hash * PRIME; @@ -116,12 +128,36 @@ static inline uint32_t fnv_1a(const uint8_t *bytes, const int length) { return hash; } -unsigned int map_hash(void *this, void *value) { +static void *internal_cig_key_ptr_from_pair_ptr(void *this, void *pair) { map_header_t *header = PTR_FROM_FIELD_PTR(map_header_t, bytes, this); - if (header->hash) { - return header->hash(value); + return &pair[header->key_offset]; +} + +// TODO: just make these internal probably +bool map_pair_key_equals(void *this, void *pair1, void *pair2) { +} + +unsigned int map_pair_hash(void *this, void *pair) { + map_header_t *header = PTR_FROM_FIELD_PTR(map_header_t, bytes, this); + void *key = internal_cig_key_ptr_from_pair_ptr(this, pair); + + // find the initial index based on hash + uint32_t index; + if (header->hash != NULL) { + index = header->hash(key) % header->mapping_capacity; + } else { + index = fnv_1a(key, header->key_size) % header->mapping_capacity; } - return (unsigned int) fnv_1a(value, header->keysize) % header->mapping_capacity; + + int existing_index = header->mapping_arr[index]; + while (!(existing_index == FLAG_FREE || existing_index == FLAG_GRAVESTONE)) { + index = fnv_1a( + (const uint8_t *) &index, + sizeof(index) + ) % header->mapping_capacity; + int existing_index = header->mapping_arr[index]; + } + return (unsigned int) index; } int map_len(void *this) { @@ -140,6 +176,6 @@ int map_cap(void *this) { return header->capacity; } -#undef FREE -#undef GRAVESTONE +#undef FLAG_FREE +#undef FLAG_GRAVESTONE diff --git a/test_arena_allocator.c b/test_arena_allocator.c index edcf2fc..7fe2176 100644 --- a/test_arena_allocator.c +++ b/test_arena_allocator.c @@ -139,7 +139,7 @@ Test(arena_allocator, resize_with_zero_capacity) { typedef struct { size_t size; size_t capacity; - size_t itemsize; + size_t item_size; allocator_t allocator; uint8_t bytes[]; } test_header_t; @@ -148,7 +148,7 @@ Test(arena_allocator, resize_with_zero_capacity) { test_header_t *header = allocator_alloc(arena, sizeof(test_header_t)); header->size = 0; header->capacity = 0; - header->itemsize = sizeof(int); + header->item_size = sizeof(int); // Resize to add space for 1 element header = allocator_resize(arena, header, sizeof(test_header_t) + sizeof(int) * 1);