good progress on maps

This commit is contained in:
2026-04-12 10:51:01 +02:00
parent 6aa10199a5
commit 83d66aaf36
4 changed files with 102 additions and 56 deletions
+24 -14
View File
@@ -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 /////////////////////////////////////////////////////////////////////////
+8 -8
View File
@@ -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);
}
+68 -32
View File
@@ -1,29 +1,29 @@
#include "cig.h"
#include <assert.h>
#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;
}
header->n_items = new_size;
// 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_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];
}
return (unsigned int) fnv_1a(value, header->keysize) % header->mapping_capacity;
// 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;
}
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
+2 -2
View File
@@ -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);