1 /*
2  * Copyright (c) 2022 Meta
3  *
4  * SPDX-License-Identifier: Apache-2.0
5  */
6 
7 /**
8  * @file
9  * @brief Hashmap (Hash Table) API
10  *
11  * Hashmaps (a.k.a Hash Tables) sacrifice space for speed. All operations
12  * on a Hashmap (insert, delete, search) are O(1) complexity (on average).
13  */
14 
15 #ifndef ZEPHYR_INCLUDE_SYS_HASH_MAP_H_
16 #define ZEPHYR_INCLUDE_SYS_HASH_MAP_H_
17 
18 #include <stdbool.h>
19 #include <stddef.h>
20 #include <stdint.h>
21 
22 #include <zephyr/kernel.h>
23 #include <zephyr/sys/hash_map_api.h>
24 #include <zephyr/sys/hash_map_cxx.h>
25 #include <zephyr/sys/hash_map_oa_lp.h>
26 #include <zephyr/sys/hash_map_sc.h>
27 
28 #ifdef __cplusplus
29 extern "C" {
30 #endif
31 
32 /**
33  * @ingroup hashmap_apis
34  * @{
35  */
36 
37 /**
38  * @brief Declare a Hashmap (advanced)
39  *
40  * Declare a Hashmap with control over advanced parameters.
41  *
42  * @note The allocator @p _alloc is used for allocating internal Hashmap
43  * entries and does not interact with any user-provided keys or values.
44  *
45  * @param _name Name of the Hashmap.
46  * @param _api API pointer of type @ref sys_hashmap_api.
47  * @param _config_type Variant of @ref sys_hashmap_config.
48  * @param _data_type Variant of @ref sys_hashmap_data.
49  * @param _hash_func Hash function pointer of type @ref sys_hash_func32_t.
50  * @param _alloc_func Allocator function pointer of type @ref sys_hashmap_allocator_t.
51  * @param ... Variant-specific details for @p _config_type.
52  */
53 #define SYS_HASHMAP_DEFINE_ADVANCED(_name, _api, _config_type, _data_type, _hash_func,             \
54 				    _alloc_func, ...)                                              \
55 	const struct _config_type _name##_config = __VA_ARGS__;                                    \
56 	struct _data_type _name##_data;                                                            \
57 	struct sys_hashmap _name = {                                                               \
58 		.api = (const struct sys_hashmap_api *)(_api),                                     \
59 		.config = (const struct sys_hashmap_config *)&_name##_config,                      \
60 		.data = (struct sys_hashmap_data *)&_name##_data,                                  \
61 		.hash_func = (_hash_func),                                                         \
62 		.alloc_func = (_alloc_func),                                                       \
63 	}
64 
65 /**
66  * @brief Declare a Hashmap (advanced)
67  *
68  * Declare a Hashmap with control over advanced parameters.
69  *
70  * @note The allocator @p _alloc is used for allocating internal Hashmap
71  * entries and does not interact with any user-provided keys or values.
72  *
73  * @param _name Name of the Hashmap.
74  * @param _api API pointer of type @ref sys_hashmap_api.
75  * @param _config_type Variant of @ref sys_hashmap_config.
76  * @param _data_type Variant of @ref sys_hashmap_data.
77  * @param _hash_func Hash function pointer of type @ref sys_hash_func32_t.
78  * @param _alloc_func Allocator function pointer of type @ref sys_hashmap_allocator_t.
79  * @param ... Variant-specific details for @p _config_type.
80  */
81 #define SYS_HASHMAP_DEFINE_STATIC_ADVANCED(_name, _api, _config_type, _data_type, _hash_func,      \
82 					   _alloc_func, ...)                                       \
83 	static const struct _config_type _name##_config = __VA_ARGS__;                             \
84 	static struct _data_type _name##_data;                                                     \
85 	static struct sys_hashmap _name = {                                                        \
86 		.api = (const struct sys_hashmap_api *)(_api),                                     \
87 		.config = (const struct sys_hashmap_config *)&_name##_config,                      \
88 		.data = (struct sys_hashmap_data *)&_name##_data,                                  \
89 		.hash_func = (_hash_func),                                                         \
90 		.alloc_func = (_alloc_func),                                                       \
91 	}
92 
93 /**
94  * @brief Declare a Hashmap
95  *
96  * Declare a Hashmap with default parameters.
97  *
98  * @param _name Name of the Hashmap.
99  */
100 #define SYS_HASHMAP_DEFINE(_name) SYS_HASHMAP_DEFAULT_DEFINE(_name)
101 
102 /**
103  * @brief Declare a Hashmap statically
104  *
105  * Declare a Hashmap statically with default parameters.
106  *
107  * @param _name Name of the Hashmap.
108  */
109 #define SYS_HASHMAP_DEFINE_STATIC(_name) SYS_HASHMAP_DEFAULT_DEFINE_STATIC(_name)
110 
111 /*
112  * A safe wrapper for realloc(), invariant of which libc provides it.
113  */
sys_hashmap_default_allocator(void * ptr,size_t size)114 static inline void *sys_hashmap_default_allocator(void *ptr, size_t size)
115 {
116 	if (size == 0) {
117 		free(ptr);
118 		return NULL;
119 	}
120 
121 	return realloc(ptr, size);
122 }
123 
124 #define SYS_HASHMAP_DEFAULT_ALLOCATOR sys_hashmap_default_allocator
125 
126 /** @brief The default Hashmap load factor (in hundredths) */
127 #define SYS_HASHMAP_DEFAULT_LOAD_FACTOR 75
128 
129 /** @brief Generic Hashmap */
130 struct sys_hashmap {
131 	const struct sys_hashmap_api *api;
132 	const struct sys_hashmap_config *config;
133 	struct sys_hashmap_data *data;
134 	sys_hash_func32_t hash_func;
135 	sys_hashmap_allocator_t alloc_func;
136 };
137 
138 /**
139  * @brief Iterate over all values contained in a @ref sys_hashmap
140  *
141  * @param map Hashmap to iterate over
142  * @param cb Callback to call for each entry
143  * @param cookie User-specified variable
144  */
sys_hashmap_foreach(const struct sys_hashmap * map,sys_hashmap_callback_t cb,void * cookie)145 static inline void sys_hashmap_foreach(const struct sys_hashmap *map, sys_hashmap_callback_t cb,
146 				       void *cookie)
147 {
148 	struct sys_hashmap_iterator it = {0};
149 
150 	for (map->api->iter(map, &it); sys_hashmap_iterator_has_next(&it);) {
151 		it.next(&it);
152 		cb(it.key, it.value, cookie);
153 	}
154 }
155 
156 /**
157  * @brief Clear all entries contained in a @ref sys_hashmap
158  *
159  * @note If the values in a particular Hashmap are
160  *
161  * @param map Hashmap to clear
162  * @param cb Callback to call for each entry
163  * @param cookie User-specified variable
164  */
sys_hashmap_clear(struct sys_hashmap * map,sys_hashmap_callback_t cb,void * cookie)165 static inline void sys_hashmap_clear(struct sys_hashmap *map, sys_hashmap_callback_t cb,
166 				     void *cookie)
167 {
168 	map->api->clear(map, cb, cookie);
169 }
170 
171 /**
172  * @brief Insert a new entry into a @ref sys_hashmap
173  *
174  * Insert a new @p key - @p value pair into @p map.
175  *
176  * @param map Hashmap to insert into
177  * @param key Key to associate with @p value
178  * @param value Value to associate with @p key
179  * @param old_value Location to store the value previously associated with @p key or `NULL`
180  * @retval 0 if @p value was inserted for an existing key, in which case @p old_value will contain
181  * the previous value
182  * @retval 1 if a new entry was inserted for the @p key - @p value pair
183  * @retval -ENOMEM if memory allocation failed
184  * @retval -ENOSPC if the size limit has been reached
185  */
sys_hashmap_insert(struct sys_hashmap * map,uint64_t key,uint64_t value,uint64_t * old_value)186 static inline int sys_hashmap_insert(struct sys_hashmap *map, uint64_t key, uint64_t value,
187 				     uint64_t *old_value)
188 {
189 	return map->api->insert(map, key, value, old_value);
190 }
191 
192 /**
193  * @brief Remove an entry from a @ref sys_hashmap
194  *
195  * Erase the entry associated with key @p key, if one exists.
196  *
197  * @param map Hashmap to remove from
198  * @param key Key to remove from @p map
199  * @param value Location to store a potential value associated with @p key or `NULL`
200  *
201  * @retval true if @p map was modified as a result of this operation.
202  * @retval false if @p map does not contain a value associated with @p key.
203  */
sys_hashmap_remove(struct sys_hashmap * map,uint64_t key,uint64_t * value)204 static inline bool sys_hashmap_remove(struct sys_hashmap *map, uint64_t key, uint64_t *value)
205 {
206 	return map->api->remove(map, key, value);
207 }
208 
209 /**
210  * @brief Get a value from a @ref sys_hashmap
211  *
212  * Look-up the @ref uint64_t associated with @p key, if one exists.
213  *
214  * @param map Hashmap to search through
215  * @param key Key with which to search @p map
216  * @param value Location to store a potential value associated with @p key or `NULL`
217  *
218  * @retval true if @p map contains a value associated with @p key.
219  * @retval false if @p map does not contain a value associated with @p key.
220  */
sys_hashmap_get(const struct sys_hashmap * map,uint64_t key,uint64_t * value)221 static inline bool sys_hashmap_get(const struct sys_hashmap *map, uint64_t key, uint64_t *value)
222 {
223 	return map->api->get(map, key, value);
224 }
225 
226 /**
227  * @brief Check if @p map contains a value associated with @p key
228  *
229  * @param map Hashmap to search through
230  * @param key Key with which to search @p map
231  *
232  * @retval true if @p map contains a value associated with @p key.
233  * @retval false if @p map does not contain a value associated with @p key.
234  */
sys_hashmap_contains_key(const struct sys_hashmap * map,uint64_t key)235 static inline bool sys_hashmap_contains_key(const struct sys_hashmap *map, uint64_t key)
236 {
237 	return sys_hashmap_get(map, key, NULL);
238 }
239 
240 /**
241  * @brief Query the number of entries contained within @p map
242  *
243  * @param map Hashmap to search through
244  *
245  * @return the number of entries contained within @p map.
246  */
sys_hashmap_size(const struct sys_hashmap * map)247 static inline size_t sys_hashmap_size(const struct sys_hashmap *map)
248 {
249 	return map->data->size;
250 }
251 
252 /**
253  * @brief Check if @p map is empty
254  *
255  * @param map Hashmap to query
256  *
257  * @retval true if @p map is empty.
258  * @retval false if @p map is not empty.
259  */
sys_hashmap_is_empty(const struct sys_hashmap * map)260 static inline bool sys_hashmap_is_empty(const struct sys_hashmap *map)
261 {
262 	return map->data->size == 0;
263 }
264 
265 /**
266  * @brief Query the load factor of @p map
267  *
268  * @note To convert the load factor to a floating-point value use
269  * `sys_hash_load_factor(map) / 100.0f`.
270  *
271  * @param map Hashmap to query
272  *
273  * @return Load factor of @p map expressed in hundredths.
274  */
sys_hashmap_load_factor(const struct sys_hashmap * map)275 static inline uint8_t sys_hashmap_load_factor(const struct sys_hashmap *map)
276 {
277 	if (map->data->n_buckets == 0) {
278 		return 0;
279 	}
280 
281 	return (map->data->size * 100) / map->data->n_buckets;
282 }
283 
284 /**
285  * @brief Query the number of buckets used in @p map
286  *
287  * @param map Hashmap to query
288  * @return Number of buckets used in @p map
289  */
sys_hashmap_num_buckets(const struct sys_hashmap * map)290 static inline size_t sys_hashmap_num_buckets(const struct sys_hashmap *map)
291 {
292 	return map->data->n_buckets;
293 }
294 
295 /**
296  * @brief Decide whether the Hashmap should be resized
297  *
298  * This is a simple opportunistic method that implementations
299  * can choose to use. It will grow and shrink the Hashmap by a factor
300  * of 2 when insertion / removal would exceed / fall into the specified
301  * load factor.
302  *
303  * @note Users should call this prior to inserting a new key-value pair and after removing a
304  * key-value pair.
305  *
306  * @note The number of reserved entries is implementation-defined, but it is only considered
307  * as part of the load factor when growing the hash table.
308  *
309  * @param map Hashmap to examine
310  * @param grow true if an entry is to be added. false if an entry has been removed
311  * @param num_reserved the number of reserved entries
312  * @param[out] new_num_buckets variable Hashmap size
313  * @return true if the Hashmap should be rehashed
314  * @return false if the Hashmap should not be rehashed
315  */
sys_hashmap_should_rehash(const struct sys_hashmap * map,bool grow,size_t num_reserved,size_t * new_num_buckets)316 static inline bool sys_hashmap_should_rehash(const struct sys_hashmap *map, bool grow,
317 					     size_t num_reserved, size_t *new_num_buckets)
318 {
319 	size_t size;
320 	bool should_grow;
321 	size_t n_buckets;
322 	bool should_shrink;
323 	const bool shrink = !grow;
324 	struct sys_hashmap_oa_lp_data *const data = (struct sys_hashmap_oa_lp_data *)map->data;
325 	const struct sys_hashmap_config *const config = map->config;
326 
327 	/* All branchless calculations, so very cache-friendly */
328 
329 	/* calculate new size */
330 	size = data->size;
331 	size += grow;
332 	/* maximum size imposed by the implementation */
333 	__ASSERT_NO_MSG(size < SIZE_MAX / 100);
334 
335 	/* calculate new number of buckets */
336 	n_buckets = data->n_buckets;
337 	/* initial number of buckets */
338 	n_buckets += grow * (size == 1) * config->initial_n_buckets;
339 	/* grow at a rate of 2x */
340 	n_buckets <<= grow * (size != 1);
341 	/* shrink at a rate of 2x */
342 	n_buckets >>= shrink;
343 
344 	/* shrink to zero if empty */
345 	n_buckets *= (size != 0);
346 
347 	__ASSERT_NO_MSG(new_num_buckets != NULL);
348 	__ASSERT_NO_MSG(new_num_buckets != &data->n_buckets);
349 	*new_num_buckets = n_buckets;
350 
351 	should_grow =
352 		grow && (data->n_buckets == 0 ||
353 			 (size + num_reserved) * 100 / data->n_buckets > map->config->load_factor);
354 	should_shrink =
355 		shrink && (n_buckets == 0 || (size * 100) / n_buckets <= map->config->load_factor);
356 
357 	return should_grow || should_shrink;
358 }
359 
360 /** @} */
361 
362 #ifdef __cplusplus
363 }
364 #endif
365 
366 #endif /* ZEPHYR_INCLUDE_SYS_HASH_MAP_H_ */
367