1 /*
2  * Copyright (c) 2022 Meta
3  *
4  * SPDX-License-Identifier: Apache-2.0
5  */
6 
7 /**
8  * @file
9  * @addtogroup hashmap_apis
10  * @{
11  */
12 
13 #ifndef ZEPHYR_INCLUDE_SYS_HASH_MAP_H_
14 #define ZEPHYR_INCLUDE_SYS_HASH_MAP_H_
15 
16 #include <stdbool.h>
17 #include <stddef.h>
18 #include <stdint.h>
19 #include <stdlib.h>
20 
21 #include <zephyr/kernel.h>
22 #include <zephyr/sys/hash_map_api.h>
23 #include <zephyr/sys/hash_map_cxx.h>
24 #include <zephyr/sys/hash_map_oa_lp.h>
25 #include <zephyr/sys/hash_map_sc.h>
26 
27 #ifdef __cplusplus
28 extern "C" {
29 #endif
30 
31 /**
32  * @brief Declare a Hashmap (advanced)
33  *
34  * Declare a Hashmap with control over advanced parameters.
35  *
36  * @note The allocator @p _alloc is used for allocating internal Hashmap
37  * entries and does not interact with any user-provided keys or values.
38  *
39  * @param _name Name of the Hashmap.
40  * @param _api API pointer of type @ref sys_hashmap_api.
41  * @param _config_type Variant of @ref sys_hashmap_config.
42  * @param _data_type Variant of @ref sys_hashmap_data.
43  * @param _hash_func Hash function pointer of type @ref sys_hash_func32_t.
44  * @param _alloc_func Allocator function pointer of type @ref sys_hashmap_allocator_t.
45  * @param ... Variant-specific details for @p _config_type.
46  */
47 #define SYS_HASHMAP_DEFINE_ADVANCED(_name, _api, _config_type, _data_type, _hash_func,             \
48 				    _alloc_func, ...)                                              \
49 	const struct _config_type _name##_config = __VA_ARGS__;                                    \
50 	struct _data_type _name##_data;                                                            \
51 	struct sys_hashmap _name = {                                                               \
52 		.api = (const struct sys_hashmap_api *)(_api),                                     \
53 		.config = (const struct sys_hashmap_config *)&_name##_config,                      \
54 		.data = (struct sys_hashmap_data *)&_name##_data,                                  \
55 		.hash_func = (_hash_func),                                                         \
56 		.alloc_func = (_alloc_func),                                                       \
57 	}
58 
59 /**
60  * @brief Declare a Hashmap statically (advanced)
61  *
62  * Declare a Hashmap statically with control over advanced parameters.
63  *
64  * @note The allocator @p _alloc is used for allocating internal Hashmap
65  * entries and does not interact with any user-provided keys or values.
66  *
67  * @param _name Name of the Hashmap.
68  * @param _api API pointer of type @ref sys_hashmap_api.
69  * @param _config_type Variant of @ref sys_hashmap_config.
70  * @param _data_type Variant of @ref sys_hashmap_data.
71  * @param _hash_func Hash function pointer of type @ref sys_hash_func32_t.
72  * @param _alloc_func Allocator function pointer of type @ref sys_hashmap_allocator_t.
73  * @param ... Variant-specific details for @p _config_type.
74  */
75 #define SYS_HASHMAP_DEFINE_STATIC_ADVANCED(_name, _api, _config_type, _data_type, _hash_func,      \
76 					   _alloc_func, ...)                                       \
77 	static const struct _config_type _name##_config = __VA_ARGS__;                             \
78 	static struct _data_type _name##_data;                                                     \
79 	static struct sys_hashmap _name = {                                                        \
80 		.api = (const struct sys_hashmap_api *)(_api),                                     \
81 		.config = (const struct sys_hashmap_config *)&_name##_config,                      \
82 		.data = (struct sys_hashmap_data *)&_name##_data,                                  \
83 		.hash_func = (_hash_func),                                                         \
84 		.alloc_func = (_alloc_func),                                                       \
85 	}
86 
87 /**
88  * @brief Declare a Hashmap
89  *
90  * Declare a Hashmap with default parameters.
91  *
92  * @param _name Name of the Hashmap.
93  */
94 #define SYS_HASHMAP_DEFINE(_name) SYS_HASHMAP_DEFAULT_DEFINE(_name)
95 
96 /**
97  * @brief Declare a Hashmap statically
98  *
99  * Declare a Hashmap statically with default parameters.
100  *
101  * @param _name Name of the Hashmap.
102  */
103 #define SYS_HASHMAP_DEFINE_STATIC(_name) SYS_HASHMAP_DEFAULT_DEFINE_STATIC(_name)
104 
105 /*
106  * A safe wrapper for realloc(), invariant of which libc provides it.
107  */
sys_hashmap_default_allocator(void * ptr,size_t size)108 static inline void *sys_hashmap_default_allocator(void *ptr, size_t size)
109 {
110 	if (size == 0) {
111 		free(ptr);
112 		return NULL;
113 	}
114 
115 	return realloc(ptr, size);
116 }
117 
118 /** @brief The default Hashmap allocator */
119 #define SYS_HASHMAP_DEFAULT_ALLOCATOR sys_hashmap_default_allocator
120 
121 /** @brief The default Hashmap load factor (in hundredths) */
122 #define SYS_HASHMAP_DEFAULT_LOAD_FACTOR 75
123 
124 /** @brief Generic Hashmap */
125 struct sys_hashmap {
126 	/** Hashmap API */
127 	const struct sys_hashmap_api *api;
128 	/** Hashmap configuration */
129 	const struct sys_hashmap_config *config;
130 	/** Hashmap data */
131 	struct sys_hashmap_data *data;
132 	/** Hash function */
133 	sys_hash_func32_t hash_func;
134 	/** Allocator */
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