1 // SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause)
2
3 /*
4 * Generic non-thread safe hash map implementation.
5 *
6 * Copyright (c) 2019 Facebook
7 */
8 #include <stdint.h>
9 #include <stdlib.h>
10 #include <stdio.h>
11 #include <errno.h>
12 #include <linux/err.h>
13 #include "hashmap.h"
14
15 /* start with 4 buckets */
16 #define HASHMAP_MIN_CAP_BITS 2
17
hashmap_add_entry(struct hashmap_entry ** pprev,struct hashmap_entry * entry)18 static void hashmap_add_entry(struct hashmap_entry **pprev,
19 struct hashmap_entry *entry)
20 {
21 entry->next = *pprev;
22 *pprev = entry;
23 }
24
hashmap_del_entry(struct hashmap_entry ** pprev,struct hashmap_entry * entry)25 static void hashmap_del_entry(struct hashmap_entry **pprev,
26 struct hashmap_entry *entry)
27 {
28 *pprev = entry->next;
29 entry->next = NULL;
30 }
31
hashmap__init(struct hashmap * map,hashmap_hash_fn hash_fn,hashmap_equal_fn equal_fn,void * ctx)32 void hashmap__init(struct hashmap *map, hashmap_hash_fn hash_fn,
33 hashmap_equal_fn equal_fn, void *ctx)
34 {
35 map->hash_fn = hash_fn;
36 map->equal_fn = equal_fn;
37 map->ctx = ctx;
38
39 map->buckets = NULL;
40 map->cap = 0;
41 map->cap_bits = 0;
42 map->sz = 0;
43 }
44
hashmap__new(hashmap_hash_fn hash_fn,hashmap_equal_fn equal_fn,void * ctx)45 struct hashmap *hashmap__new(hashmap_hash_fn hash_fn,
46 hashmap_equal_fn equal_fn,
47 void *ctx)
48 {
49 struct hashmap *map = malloc(sizeof(struct hashmap));
50
51 if (!map)
52 return ERR_PTR(-ENOMEM);
53 hashmap__init(map, hash_fn, equal_fn, ctx);
54 return map;
55 }
56
hashmap__clear(struct hashmap * map)57 void hashmap__clear(struct hashmap *map)
58 {
59 struct hashmap_entry *cur, *tmp;
60 int bkt;
61
62 hashmap__for_each_entry_safe(map, cur, tmp, bkt) {
63 free(cur);
64 }
65 free(map->buckets);
66 map->buckets = NULL;
67 map->cap = map->cap_bits = map->sz = 0;
68 }
69
hashmap__free(struct hashmap * map)70 void hashmap__free(struct hashmap *map)
71 {
72 if (!map)
73 return;
74
75 hashmap__clear(map);
76 free(map);
77 }
78
hashmap__size(const struct hashmap * map)79 size_t hashmap__size(const struct hashmap *map)
80 {
81 return map->sz;
82 }
83
hashmap__capacity(const struct hashmap * map)84 size_t hashmap__capacity(const struct hashmap *map)
85 {
86 return map->cap;
87 }
88
hashmap_needs_to_grow(struct hashmap * map)89 static bool hashmap_needs_to_grow(struct hashmap *map)
90 {
91 /* grow if empty or more than 75% filled */
92 return (map->cap == 0) || ((map->sz + 1) * 4 / 3 > map->cap);
93 }
94
hashmap_grow(struct hashmap * map)95 static int hashmap_grow(struct hashmap *map)
96 {
97 struct hashmap_entry **new_buckets;
98 struct hashmap_entry *cur, *tmp;
99 size_t new_cap_bits, new_cap;
100 size_t h;
101 int bkt;
102
103 new_cap_bits = map->cap_bits + 1;
104 if (new_cap_bits < HASHMAP_MIN_CAP_BITS)
105 new_cap_bits = HASHMAP_MIN_CAP_BITS;
106
107 new_cap = 1UL << new_cap_bits;
108 new_buckets = calloc(new_cap, sizeof(new_buckets[0]));
109 if (!new_buckets)
110 return -ENOMEM;
111
112 hashmap__for_each_entry_safe(map, cur, tmp, bkt) {
113 h = hash_bits(map->hash_fn(cur->key, map->ctx), new_cap_bits);
114 hashmap_add_entry(&new_buckets[h], cur);
115 }
116
117 map->cap = new_cap;
118 map->cap_bits = new_cap_bits;
119 free(map->buckets);
120 map->buckets = new_buckets;
121
122 return 0;
123 }
124
hashmap_find_entry(const struct hashmap * map,const void * key,size_t hash,struct hashmap_entry *** pprev,struct hashmap_entry ** entry)125 static bool hashmap_find_entry(const struct hashmap *map,
126 const void *key, size_t hash,
127 struct hashmap_entry ***pprev,
128 struct hashmap_entry **entry)
129 {
130 struct hashmap_entry *cur, **prev_ptr;
131
132 if (!map->buckets)
133 return false;
134
135 for (prev_ptr = &map->buckets[hash], cur = *prev_ptr;
136 cur;
137 prev_ptr = &cur->next, cur = cur->next) {
138 if (map->equal_fn(cur->key, key, map->ctx)) {
139 if (pprev)
140 *pprev = prev_ptr;
141 *entry = cur;
142 return true;
143 }
144 }
145
146 return false;
147 }
148
hashmap__insert(struct hashmap * map,const void * key,void * value,enum hashmap_insert_strategy strategy,const void ** old_key,void ** old_value)149 int hashmap__insert(struct hashmap *map, const void *key, void *value,
150 enum hashmap_insert_strategy strategy,
151 const void **old_key, void **old_value)
152 {
153 struct hashmap_entry *entry;
154 size_t h;
155 int err;
156
157 if (old_key)
158 *old_key = NULL;
159 if (old_value)
160 *old_value = NULL;
161
162 h = hash_bits(map->hash_fn(key, map->ctx), map->cap_bits);
163 if (strategy != HASHMAP_APPEND &&
164 hashmap_find_entry(map, key, h, NULL, &entry)) {
165 if (old_key)
166 *old_key = entry->key;
167 if (old_value)
168 *old_value = entry->value;
169
170 if (strategy == HASHMAP_SET || strategy == HASHMAP_UPDATE) {
171 entry->key = key;
172 entry->value = value;
173 return 0;
174 } else if (strategy == HASHMAP_ADD) {
175 return -EEXIST;
176 }
177 }
178
179 if (strategy == HASHMAP_UPDATE)
180 return -ENOENT;
181
182 if (hashmap_needs_to_grow(map)) {
183 err = hashmap_grow(map);
184 if (err)
185 return err;
186 h = hash_bits(map->hash_fn(key, map->ctx), map->cap_bits);
187 }
188
189 entry = malloc(sizeof(struct hashmap_entry));
190 if (!entry)
191 return -ENOMEM;
192
193 entry->key = key;
194 entry->value = value;
195 hashmap_add_entry(&map->buckets[h], entry);
196 map->sz++;
197
198 return 0;
199 }
200
hashmap__find(const struct hashmap * map,const void * key,void ** value)201 bool hashmap__find(const struct hashmap *map, const void *key, void **value)
202 {
203 struct hashmap_entry *entry;
204 size_t h;
205
206 h = hash_bits(map->hash_fn(key, map->ctx), map->cap_bits);
207 if (!hashmap_find_entry(map, key, h, NULL, &entry))
208 return false;
209
210 if (value)
211 *value = entry->value;
212 return true;
213 }
214
hashmap__delete(struct hashmap * map,const void * key,const void ** old_key,void ** old_value)215 bool hashmap__delete(struct hashmap *map, const void *key,
216 const void **old_key, void **old_value)
217 {
218 struct hashmap_entry **pprev, *entry;
219 size_t h;
220
221 h = hash_bits(map->hash_fn(key, map->ctx), map->cap_bits);
222 if (!hashmap_find_entry(map, key, h, &pprev, &entry))
223 return false;
224
225 if (old_key)
226 *old_key = entry->key;
227 if (old_value)
228 *old_value = entry->value;
229
230 hashmap_del_entry(pprev, entry);
231 free(entry);
232 map->sz--;
233
234 return true;
235 }
236
237