1 /*
2  * Copyright (c) 2022 Meta
3  *
4  * SPDX-License-Identifier: Apache-2.0
5  */
6 
7 #include <errno.h>
8 #include <stdbool.h>
9 #include <stddef.h>
10 #include <stdint.h>
11 #include <stdlib.h>
12 #include <string.h>
13 
14 #include <zephyr/sys/dlist.h>
15 #include <zephyr/sys/hash_map.h>
16 #include <zephyr/sys/hash_map_sc.h>
17 #include <zephyr/sys/util.h>
18 
19 struct sys_hashmap_sc_entry {
20 	uint64_t key;
21 	uint64_t value;
22 	sys_dnode_t node;
23 };
24 
sys_hashmap_sc_entry_init(struct sys_hashmap_sc_entry * entry,uint64_t key,uint64_t value)25 static void sys_hashmap_sc_entry_init(struct sys_hashmap_sc_entry *entry, uint64_t key,
26 				      uint64_t value)
27 {
28 	entry->key = key;
29 	entry->value = value;
30 	sys_dnode_init(&entry->node);
31 }
32 
sys_hashmap_sc_insert_entry(struct sys_hashmap * map,struct sys_hashmap_sc_entry * entry)33 static void sys_hashmap_sc_insert_entry(struct sys_hashmap *map, struct sys_hashmap_sc_entry *entry)
34 {
35 	sys_dlist_t *buckets = map->data->buckets;
36 	uint32_t hash = map->hash_func(&entry->key, sizeof(entry->key));
37 
38 	sys_dlist_append(&buckets[hash % map->data->n_buckets], &entry->node);
39 	++map->data->size;
40 }
41 
sys_hashmap_sc_insert_all(struct sys_hashmap * map,sys_dlist_t * list)42 static void sys_hashmap_sc_insert_all(struct sys_hashmap *map, sys_dlist_t *list)
43 {
44 	__unused int ret;
45 	struct sys_hashmap_sc_entry *entry;
46 
47 	while (!sys_dlist_is_empty(list)) {
48 		entry = CONTAINER_OF(sys_dlist_get(list), struct sys_hashmap_sc_entry, node);
49 		sys_hashmap_sc_insert_entry(map, entry);
50 	}
51 }
52 
sys_hashmap_sc_to_list(struct sys_hashmap * map,sys_dlist_t * list)53 static void sys_hashmap_sc_to_list(struct sys_hashmap *map, sys_dlist_t *list)
54 {
55 	sys_dlist_t *bucket;
56 	struct sys_hashmap_sc_entry *entry;
57 	sys_dlist_t *buckets = map->data->buckets;
58 
59 	sys_dlist_init(list);
60 
61 	for (size_t i = 0; i < map->data->n_buckets; ++i) {
62 		bucket = &buckets[i];
63 		while (!sys_dlist_is_empty(bucket)) {
64 			entry = CONTAINER_OF(sys_dlist_get(bucket), struct sys_hashmap_sc_entry,
65 					     node);
66 			sys_dlist_append(list, &entry->node);
67 		}
68 	}
69 }
70 
sys_hashmap_sc_rehash(struct sys_hashmap * map,bool grow)71 static int sys_hashmap_sc_rehash(struct sys_hashmap *map, bool grow)
72 {
73 	sys_dlist_t list;
74 	sys_dlist_t *bucket;
75 	size_t new_n_buckets;
76 	sys_dlist_t *new_buckets;
77 
78 	if (!sys_hashmap_should_rehash(map, grow, 0, &new_n_buckets)) {
79 		return 0;
80 	}
81 
82 	/* extract all entries from the hashmap */
83 	sys_hashmap_sc_to_list(map, &list);
84 
85 	/* reallocate memory */
86 	new_buckets = (sys_dlist_t *)map->alloc_func(map->data->buckets,
87 						     new_n_buckets * sizeof(*new_buckets));
88 	if (new_buckets == NULL && new_n_buckets != 0) {
89 		/* re-insert all entries into the hashmap if reallocation fails */
90 		sys_hashmap_sc_insert_all(map, &list);
91 		return -ENOMEM;
92 	}
93 
94 	/* ensure all buckets are empty / initialized */
95 	map->data->size = 0;
96 	map->data->buckets = new_buckets;
97 	map->data->n_buckets = new_n_buckets;
98 	for (size_t i = 0; i < new_n_buckets; ++i) {
99 		bucket = &((sys_dlist_t *)(map->data->buckets))[i];
100 		sys_dlist_init(bucket);
101 	}
102 
103 	/* re-insert all entries into the hashmap */
104 	sys_hashmap_sc_insert_all(map, &list);
105 
106 	return 0;
107 }
108 
sys_hashmap_sc_find(const struct sys_hashmap * map,uint64_t key)109 static struct sys_hashmap_sc_entry *sys_hashmap_sc_find(const struct sys_hashmap *map, uint64_t key)
110 {
111 	uint32_t hash;
112 	sys_dlist_t *bucket;
113 	sys_dlist_t *buckets;
114 	struct sys_hashmap_sc_entry *entry;
115 
116 	if (map->data->n_buckets == 0) {
117 		return NULL;
118 	}
119 
120 	__ASSERT_NO_MSG(map->data->size > 0);
121 
122 	hash = map->hash_func(&key, sizeof(key));
123 	buckets = (sys_dlist_t *)map->data->buckets;
124 	bucket = &buckets[hash % map->data->n_buckets];
125 
126 	SYS_DLIST_FOR_EACH_CONTAINER(bucket, entry, node) {
127 		if (entry->key == key) {
128 			return entry;
129 		}
130 	}
131 
132 	return NULL;
133 }
134 
sys_hashmap_sc_iter_next(struct sys_hashmap_iterator * it)135 static void sys_hashmap_sc_iter_next(struct sys_hashmap_iterator *it)
136 {
137 	sys_dlist_t *bucket;
138 	bool found_previous_key = false;
139 	struct sys_hashmap_sc_entry *entry;
140 	const struct sys_hashmap *map = it->map;
141 	sys_dlist_t *buckets = map->data->buckets;
142 
143 	__ASSERT(it->size == map->data->size, "Concurrent modification!");
144 	__ASSERT(sys_hashmap_iterator_has_next(it), "Attempt to access beyond current bound!");
145 
146 	if (it->pos == 0) {
147 		/* at position 0, state equals the beginning of the bucket array */
148 		it->state = buckets;
149 		found_previous_key = true;
150 	}
151 
152 	for (bucket = it->state; bucket < &buckets[map->data->n_buckets]; ++bucket) {
153 		SYS_DLIST_FOR_EACH_CONTAINER(bucket, entry, node) {
154 			if (!found_previous_key) {
155 				if (entry->key == it->key) {
156 					found_previous_key = true;
157 				}
158 
159 				continue;
160 			}
161 
162 			/* save the bucket to state so we can restart scanning from a saved position
163 			 */
164 			it->state = bucket;
165 			it->key = entry->key;
166 			it->value = entry->value;
167 			++it->pos;
168 
169 			return;
170 		}
171 	}
172 
173 	__ASSERT(false, "Entire Hashmap traversed and no entry was found");
174 }
175 
176 /*
177  * Separate Chaining Hashmap API
178  */
179 
sys_hashmap_sc_iter(const struct sys_hashmap * map,struct sys_hashmap_iterator * it)180 static void sys_hashmap_sc_iter(const struct sys_hashmap *map, struct sys_hashmap_iterator *it)
181 {
182 	it->map = map;
183 	it->next = sys_hashmap_sc_iter_next;
184 	it->state = map->data->buckets;
185 	it->key = 0;
186 	it->value = 0;
187 	it->pos = 0;
188 	*((size_t *)&it->size) = map->data->size;
189 }
190 
sys_hashmap_sc_clear(struct sys_hashmap * map,sys_hashmap_callback_t cb,void * cookie)191 static void sys_hashmap_sc_clear(struct sys_hashmap *map, sys_hashmap_callback_t cb, void *cookie)
192 {
193 	sys_dlist_t list;
194 	struct sys_hashmap_sc_entry *entry;
195 
196 	sys_hashmap_sc_to_list(map, &list);
197 
198 	/* free the buckets */
199 	if (map->data->buckets != NULL) {
200 		map->alloc_func(map->data->buckets, 0);
201 		map->data->buckets = NULL;
202 	}
203 
204 	map->data->n_buckets = 0;
205 	map->data->size = 0;
206 
207 	while (!sys_dlist_is_empty(&list)) {
208 		entry = CONTAINER_OF(sys_dlist_get(&list), struct sys_hashmap_sc_entry, node);
209 
210 		/* call the callback for entry */
211 		if (cb != NULL) {
212 			cb(entry->key, entry->value, cookie);
213 		}
214 
215 		/* free the entry using the Hashmap's allocator */
216 		map->alloc_func(entry, 0);
217 	}
218 }
219 
sys_hashmap_sc_insert(struct sys_hashmap * map,uint64_t key,uint64_t value,uint64_t * old_value)220 static int sys_hashmap_sc_insert(struct sys_hashmap *map, uint64_t key, uint64_t value,
221 				 uint64_t *old_value)
222 {
223 	int ret;
224 	struct sys_hashmap_sc_entry *entry;
225 
226 	entry = sys_hashmap_sc_find(map, key);
227 	if (entry != NULL) {
228 		if (old_value != NULL) {
229 			*old_value = entry->value;
230 		}
231 
232 		entry->value = value;
233 
234 		return 0;
235 	}
236 
237 	ret = sys_hashmap_sc_rehash(map, true);
238 	if (ret < 0) {
239 		return ret;
240 	}
241 
242 	entry = map->alloc_func(NULL, sizeof(*entry));
243 	if (entry == NULL) {
244 		return -ENOMEM;
245 	}
246 
247 	sys_hashmap_sc_entry_init(entry, key, value);
248 	sys_hashmap_sc_insert_entry(map, entry);
249 
250 	return 1;
251 }
252 
sys_hashmap_sc_remove(struct sys_hashmap * map,uint64_t key,uint64_t * value)253 static bool sys_hashmap_sc_remove(struct sys_hashmap *map, uint64_t key, uint64_t *value)
254 {
255 	__unused int ret;
256 	struct sys_hashmap_sc_entry *entry;
257 
258 	entry = sys_hashmap_sc_find(map, key);
259 	if (entry == NULL) {
260 		return false;
261 	}
262 
263 	if (value != NULL) {
264 		*value = entry->value;
265 	}
266 
267 	sys_dlist_remove(&entry->node);
268 	--map->data->size;
269 
270 	ret = sys_hashmap_sc_rehash(map, false);
271 	/* Realloc to a smaller size of memory should *always* work */
272 	__ASSERT_NO_MSG(ret >= 0);
273 
274 	/* free the entry */
275 	map->alloc_func(entry, 0);
276 
277 	return true;
278 }
279 
sys_hashmap_sc_get(const struct sys_hashmap * map,uint64_t key,uint64_t * value)280 static bool sys_hashmap_sc_get(const struct sys_hashmap *map, uint64_t key, uint64_t *value)
281 {
282 	struct sys_hashmap_sc_entry *entry;
283 
284 	entry = sys_hashmap_sc_find(map, key);
285 	if (entry == NULL) {
286 		return false;
287 	}
288 
289 	if (value != NULL) {
290 		*value = entry->value;
291 	}
292 
293 	return true;
294 }
295 
296 const struct sys_hashmap_api sys_hashmap_sc_api = {
297 	.iter = sys_hashmap_sc_iter,
298 	.clear = sys_hashmap_sc_clear,
299 	.insert = sys_hashmap_sc_insert,
300 	.remove = sys_hashmap_sc_remove,
301 	.get = sys_hashmap_sc_get,
302 };
303