Coverage Report

Created: 2026-02-23 20:32

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/Users/alexjokela/projects/lattice/src/ds/hashmap.c
Line
Count
Source
1
#include "ds/hashmap.h"
2
#include <stdlib.h>
3
#include <string.h>
4
#include <assert.h>
5
6
37.1k
#define INITIAL_CAP  16
7
357k
#define LOAD_FACTOR  70  /* percent */
8
9
/* FNV-1a hash */
10
1.03M
static size_t fnv1a(const char *key) {
11
1.03M
    size_t hash = 14695981039346656037ULL;
12
8.36M
    for (const char *p = key; *p; p++) {
13
7.33M
        hash ^= (size_t)(unsigned char)*p;
14
7.33M
        hash *= 1099511628211ULL;
15
7.33M
    }
16
1.03M
    return hash;
17
1.03M
}
18
19
45.5k
static void lat_map_alloc_entries(LatMap *m, size_t cap) {
20
45.5k
    m->entries = calloc(cap, sizeof(LatMapEntry));
21
    /* Point each entry's value to its inline buffer */
22
1.58M
    for (size_t i = 0; i < cap; i++)
23
1.53M
        m->entries[i].value = m->entries[i]._ibuf;
24
45.5k
    m->cap = cap;
25
45.5k
    m->count = 0;
26
45.5k
    m->live = 0;
27
45.5k
}
28
29
37.1k
LatMap lat_map_new(size_t value_size) {
30
37.1k
    assert(value_size <= LAT_MAP_INLINE_MAX);
31
37.1k
    LatMap m;
32
37.1k
    m.value_size = value_size;
33
37.1k
    lat_map_alloc_entries(&m, INITIAL_CAP);
34
37.1k
    return m;
35
37.1k
}
36
37
37.1k
void lat_map_free(LatMap *m) {
38
1.10M
    for (size_t i = 0; i < m->cap; i++) {
39
1.06M
        if (m->entries[i].state == MAP_OCCUPIED) {
40
342k
            free(m->entries[i].key);
41
            /* value is inline — no free needed */
42
342k
        }
43
1.06M
    }
44
37.1k
    free(m->entries);
45
37.1k
    m->entries = NULL;
46
37.1k
    m->cap = 0;
47
37.1k
    m->count = 0;
48
37.1k
    m->live = 0;
49
37.1k
}
50
51
683k
static size_t lat_map_find_slot(const LatMap *m, const char *key) {
52
683k
    size_t idx = fnv1a(key) % m->cap;
53
683k
    size_t first_tombstone = (size_t)-1;
54
1.24M
    for (size_t i = 0; i < m->cap; i++) {
55
1.24M
        size_t pos = (idx + i) % m->cap;
56
1.24M
        LatMapEntry *e = &m->entries[pos];
57
1.24M
        if (e->state == MAP_EMPTY) {
58
669k
            return (first_tombstone != (size_t)-1) ? first_tombstone : pos;
59
669k
        }
60
577k
        if (e->state == MAP_TOMBSTONE) {
61
1
            if (first_tombstone == (size_t)-1) first_tombstone = pos;
62
1
            continue;
63
1
        }
64
        /* MAP_OCCUPIED */
65
577k
        if (strcmp(e->key, key) == 0) {
66
14.6k
            return pos;
67
14.6k
        }
68
577k
    }
69
0
    return (first_tombstone != (size_t)-1) ? first_tombstone : 0;
70
683k
}
71
72
8.35k
static void lat_map_rehash(LatMap *m) {
73
8.35k
    size_t old_cap = m->cap;
74
8.35k
    LatMapEntry *old = m->entries;
75
76
8.35k
    lat_map_alloc_entries(m, old_cap * 2);
77
78
480k
    for (size_t i = 0; i < old_cap; i++) {
79
472k
        if (old[i].state == MAP_OCCUPIED) {
80
326k
            size_t pos = lat_map_find_slot(m, old[i].key);
81
326k
            m->entries[pos].key = old[i].key;
82
326k
            m->entries[pos].state = MAP_OCCUPIED;
83
            /* value pointer already points to _ibuf; copy inline data */
84
326k
            memcpy(m->entries[pos]._ibuf, old[i]._ibuf, m->value_size);
85
326k
            m->count++;
86
326k
            m->live++;
87
326k
        }
88
472k
    }
89
8.35k
    free(old);
90
8.35k
}
91
92
357k
bool lat_map_set(LatMap *m, const char *key, const void *value) {
93
357k
    if ((m->count + 1) * 100 > m->cap * LOAD_FACTOR) {
94
8.35k
        lat_map_rehash(m);
95
8.35k
    }
96
97
357k
    size_t pos = lat_map_find_slot(m, key);
98
357k
    LatMapEntry *e = &m->entries[pos];
99
100
357k
    if (e->state == MAP_OCCUPIED) {
101
        /* Update existing — value is inline */
102
14.6k
        memcpy(e->value, value, m->value_size);
103
14.6k
        return false;
104
14.6k
    }
105
106
    /* New entry — value stored inline (e->value already points to e->_ibuf) */
107
357k
    bool was_empty = (e->state == MAP_EMPTY);
108
342k
    e->key = strdup(key);
109
342k
    memcpy(e->value, value, m->value_size);
110
342k
    e->state = MAP_OCCUPIED;
111
342k
    if (was_empty) m->count++;
112
342k
    m->live++;
113
342k
    return true;
114
357k
}
115
116
372k
void *lat_map_get(const LatMap *m, const char *key) {
117
372k
    if (m->live == 0) return NULL;
118
347k
    size_t idx = fnv1a(key) % m->cap;
119
836k
    for (size_t i = 0; i < m->cap; i++) {
120
836k
        size_t pos = (idx + i) % m->cap;
121
836k
        LatMapEntry *e = &m->entries[pos];
122
836k
        if (e->state == MAP_EMPTY) return NULL;
123
537k
        if (e->state == MAP_OCCUPIED && strcmp(e->key, key) == 0) {
124
48.8k
            return e->value;
125
48.8k
        }
126
537k
    }
127
0
    return NULL;
128
347k
}
129
130
3.40k
void *lat_map_get_prehashed(const LatMap *m, const char *key, size_t hash) {
131
3.40k
    if (m->live == 0) return NULL;
132
3.40k
    size_t idx = hash % m->cap;
133
4.78k
    for (size_t i = 0; i < m->cap; i++) {
134
4.78k
        size_t pos = (idx + i) % m->cap;
135
4.78k
        LatMapEntry *e = &m->entries[pos];
136
4.78k
        if (e->state == MAP_EMPTY) return NULL;
137
4.78k
        if (e->state == MAP_OCCUPIED && strcmp(e->key, key) == 0) {
138
3.39k
            return e->value;
139
3.39k
        }
140
4.78k
    }
141
0
    return NULL;
142
3.40k
}
143
144
15
bool lat_map_remove(LatMap *m, const char *key) {
145
15
    if (m->live == 0) return false;
146
12
    size_t idx = fnv1a(key) % m->cap;
147
12
    for (size_t i = 0; i < m->cap; i++) {
148
12
        size_t pos = (idx + i) % m->cap;
149
12
        LatMapEntry *e = &m->entries[pos];
150
12
        if (e->state == MAP_EMPTY) return false;
151
12
        if (e->state == MAP_OCCUPIED && strcmp(e->key, key) == 0) {
152
12
            free(e->key);
153
12
            e->key = NULL;
154
            /* value is inline, no free needed */
155
12
            e->state = MAP_TOMBSTONE;
156
12
            m->live--;
157
12
            return true;
158
12
        }
159
12
    }
160
0
    return false;
161
12
}
162
163
208
bool lat_map_contains(const LatMap *m, const char *key) {
164
208
    return lat_map_get(m, key) != NULL;
165
208
}
166
167
101
size_t lat_map_len(const LatMap *m) {
168
101
    return m->live;
169
101
}
170
171
75.1k
void lat_map_iter(const LatMap *m, LatMapIterFn fn, void *ctx) {
172
1.72M
    for (size_t i = 0; i < m->cap; i++) {
173
1.64M
        if (m->entries[i].state == MAP_OCCUPIED) {
174
356k
            fn(m->entries[i].key, m->entries[i].value, ctx);
175
356k
        }
176
1.64M
    }
177
75.1k
}