From 0bda7b30df5b3215328e23d1b88c78270c59ab9c Mon Sep 17 00:00:00 2001 From: Chris Friedt Date: Fri, 30 Dec 2022 14:18:05 -0500 Subject: [PATCH] lib: os: add hashmap support * Add a flexible Hashmap API * Add a Separate-Chaining Hashmap Implementation * Add a Open-Addressing Linear Probe Hashmap Implementation * Add a C-Wrapper for `std::unordered_map` for benchmarking Signed-off-by: Chris Friedt --- include/zephyr/sys/hash_map.h | 366 ++++++++++++++++++++++++++++ include/zephyr/sys/hash_map_api.h | 242 ++++++++++++++++++ include/zephyr/sys/hash_map_cxx.h | 102 ++++++++ include/zephyr/sys/hash_map_oa_lp.h | 107 ++++++++ include/zephyr/sys/hash_map_sc.h | 102 ++++++++ lib/os/CMakeLists.txt | 4 + lib/os/Kconfig | 1 + lib/os/Kconfig.hash_map | 70 ++++++ lib/os/hash_map_cxx.cpp | 157 ++++++++++++ lib/os/hash_map_oa_lp.c | 293 ++++++++++++++++++++++ lib/os/hash_map_sc.c | 302 +++++++++++++++++++++++ 11 files changed, 1746 insertions(+) create mode 100644 include/zephyr/sys/hash_map.h create mode 100644 include/zephyr/sys/hash_map_api.h create mode 100644 include/zephyr/sys/hash_map_cxx.h create mode 100644 include/zephyr/sys/hash_map_oa_lp.h create mode 100644 include/zephyr/sys/hash_map_sc.h create mode 100644 lib/os/Kconfig.hash_map create mode 100644 lib/os/hash_map_cxx.cpp create mode 100644 lib/os/hash_map_oa_lp.c create mode 100644 lib/os/hash_map_sc.c diff --git a/include/zephyr/sys/hash_map.h b/include/zephyr/sys/hash_map.h new file mode 100644 index 0000000000..9c4f5d08e6 --- /dev/null +++ b/include/zephyr/sys/hash_map.h @@ -0,0 +1,366 @@ +/* + * Copyright (c) 2022 Meta + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * @file + * @brief Hashmap (Hash Table) API + * + * Hashmaps (a.k.a Hash Tables) sacrifice space for speed. All operations + * on a Hashmap (insert, delete, search) are O(1) complexity (on average). + */ + +#ifndef ZEPHYR_INCLUDE_SYS_HASH_MAP_H_ +#define ZEPHYR_INCLUDE_SYS_HASH_MAP_H_ + +#include +#include +#include + +#include +#include +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @ingroup hashmap_apis + * @{ + */ + +/** + * @brief Declare a Hashmap (advanced) + * + * Declare a Hashmap with control over advanced parameters. + * + * @note The allocator @p _alloc is used for allocating internal Hashmap + * entries and does not interact with any user-provided keys or values. + * + * @param _name Name of the Hashmap. + * @param _api API pointer of type @ref sys_hashmap_api. + * @param _config_type Variant of @ref sys_hashmap_config. + * @param _data_type Variant of @ref sys_hashmap_data. + * @param _hash_func Hash function pointer of type @ref sys_hash_func32_t. + * @param _alloc_func Allocator function pointer of type @ref sys_hashmap_allocator_t. + * @param ... Variant-specific details for @p _config_type. + */ +#define SYS_HASHMAP_DEFINE_ADVANCED(_name, _api, _config_type, _data_type, _hash_func, \ + _alloc_func, ...) \ + const struct _config_type _name##_config = __VA_ARGS__; \ + struct _data_type _name##_data; \ + struct sys_hashmap _name = { \ + .api = (const struct sys_hashmap_api *)(_api), \ + .config = (const struct sys_hashmap_config *)&_name##_config, \ + .data = (struct sys_hashmap_data *)&_name##_data, \ + .hash_func = (_hash_func), \ + .alloc_func = (_alloc_func), \ + } + +/** + * @brief Declare a Hashmap (advanced) + * + * Declare a Hashmap with control over advanced parameters. + * + * @note The allocator @p _alloc is used for allocating internal Hashmap + * entries and does not interact with any user-provided keys or values. + * + * @param _name Name of the Hashmap. + * @param _api API pointer of type @ref sys_hashmap_api. + * @param _config_type Variant of @ref sys_hashmap_config. + * @param _data_type Variant of @ref sys_hashmap_data. + * @param _hash_func Hash function pointer of type @ref sys_hash_func32_t. + * @param _alloc_func Allocator function pointer of type @ref sys_hashmap_allocator_t. + * @param ... Variant-specific details for @p _config_type. + */ +#define SYS_HASHMAP_DEFINE_STATIC_ADVANCED(_name, _api, _config_type, _data_type, _hash_func, \ + _alloc_func, ...) \ + static const struct _config_type _name##_config = __VA_ARGS__; \ + static struct _data_type _name##_data; \ + static struct sys_hashmap _name = { \ + .api = (const struct sys_hashmap_api *)(_api), \ + .config = (const struct sys_hashmap_config *)&_name##_config, \ + .data = (struct sys_hashmap_data *)&_name##_data, \ + .hash_func = (_hash_func), \ + .alloc_func = (_alloc_func), \ + } + +/** + * @brief Declare a Hashmap + * + * Declare a Hashmap with default parameters. + * + * @param _name Name of the Hashmap. + */ +#define SYS_HASHMAP_DEFINE(_name) SYS_HASHMAP_DEFAULT_DEFINE(_name) + +/** + * @brief Declare a Hashmap statically + * + * Declare a Hashmap statically with default parameters. + * + * @param _name Name of the Hashmap. + */ +#define SYS_HASHMAP_DEFINE_STATIC(_name) SYS_HASHMAP_DEFAULT_DEFINE_STATIC(_name) + +/* + * A safe wrapper for realloc(), invariant of which libc provides it. + */ +static inline void *sys_hashmap_default_allocator(void *ptr, size_t size) +{ + if (size == 0) { + free(ptr); + return NULL; + } + + return realloc(ptr, size); +} + +#define SYS_HASHMAP_DEFAULT_ALLOCATOR sys_hashmap_default_allocator + +/** @brief The default Hashmap load factor (in hundredths) */ +#define SYS_HASHMAP_DEFAULT_LOAD_FACTOR 75 + +/** @brief Generic Hashmap */ +struct sys_hashmap { + const struct sys_hashmap_api *api; + const struct sys_hashmap_config *config; + struct sys_hashmap_data *data; + sys_hash_func32_t hash_func; + sys_hashmap_allocator_t alloc_func; +}; + +/** + * @brief Iterate over all values contained in a @ref sys_hashmap + * + * @param map Hashmap to iterate over + * @param cb Callback to call for each entry + * @param cookie User-specified variable + */ +static inline void sys_hashmap_foreach(const struct sys_hashmap *map, sys_hashmap_callback_t cb, + void *cookie) +{ + struct sys_hashmap_iterator it = {0}; + + for (map->api->iter(map, &it); sys_hashmap_iterator_has_next(&it);) { + it.next(&it); + cb(it.key, it.value, cookie); + } +} + +/** + * @brief Clear all entries contained in a @ref sys_hashmap + * + * @note If the values in a particular Hashmap are + * + * @param map Hashmap to clear + * @param cb Callback to call for each entry + * @param cookie User-specified variable + */ +static inline void sys_hashmap_clear(struct sys_hashmap *map, sys_hashmap_callback_t cb, + void *cookie) +{ + map->api->clear(map, cb, cookie); +} + +/** + * @brief Insert a new entry into a @ref sys_hashmap + * + * Insert a new @p key - @p value pair into @p map. + * + * @param map Hashmap to insert into + * @param key Key to associate with @p value + * @param value Value to associate with @p key + * @param old_value Location to store the value previously associated with @p key or `NULL` + * @retval 0 if @p value was inserted for an existing key, in which case @p old_value will contain + * the previous value + * @retval 1 if a new entry was inserted for the @p key - @p value pair + * @retval -ENOMEM if memory allocation failed + * @retval -ENOSPC if the size limit has been reached + */ +static inline int sys_hashmap_insert(struct sys_hashmap *map, uint64_t key, uint64_t value, + uint64_t *old_value) +{ + return map->api->insert(map, key, value, old_value); +} + +/** + * @brief Remove an entry from a @ref sys_hashmap + * + * Erase the entry associated with key @p key, if one exists. + * + * @param map Hashmap to remove from + * @param key Key to remove from @p map + * @param value Location to store a potential value associated with @p key or `NULL` + * + * @retval true if @p map was modified as a result of this operation. + * @retval false if @p map does not contain a value associated with @p key. + */ +static inline bool sys_hashmap_remove(struct sys_hashmap *map, uint64_t key, uint64_t *value) +{ + return map->api->remove(map, key, value); +} + +/** + * @brief Get a value from a @ref sys_hashmap + * + * Look-up the @ref uint64_t associated with @p key, if one exists. + * + * @param map Hashmap to search through + * @param key Key with which to search @p map + * @param value Location to store a potential value associated with @p key or `NULL` + * + * @retval true if @p map contains a value associated with @p key. + * @retval false if @p map does not contain a value associated with @p key. + */ +static inline bool sys_hashmap_get(const struct sys_hashmap *map, uint64_t key, uint64_t *value) +{ + return map->api->get(map, key, value); +} + +/** + * @brief Check if @p map contains a value associated with @p key + * + * @param map Hashmap to search through + * @param key Key with which to search @p map + * + * @retval true if @p map contains a value associated with @p key. + * @retval false if @p map does not contain a value associated with @p key. + */ +static inline bool sys_hashmap_contains_key(const struct sys_hashmap *map, uint64_t key) +{ + return sys_hashmap_get(map, key, NULL); +} + +/** + * @brief Query the number of entries contained within @p map + * + * @param map Hashmap to search through + * + * @return the number of entries contained within @p map. + */ +static inline size_t sys_hashmap_size(const struct sys_hashmap *map) +{ + return map->data->size; +} + +/** + * @brief Check if @p map is empty + * + * @param map Hashmap to query + * + * @retval true if @p map is empty. + * @retval false if @p map is not empty. + */ +static inline bool sys_hashmap_is_empty(const struct sys_hashmap *map) +{ + return map->data->size == 0; +} + +/** + * @brief Query the load factor of @p map + * + * @note To convert the load factor to a floating-point value use + * `sys_hash_load_factor(map) / 100.0f`. + * + * @param map Hashmap to query + * + * @return Load factor of @p map expressed in hundredths. + */ +static inline uint8_t sys_hashmap_load_factor(const struct sys_hashmap *map) +{ + if (map->data->n_buckets == 0) { + return 0; + } + + return (map->data->size * 100) / map->data->n_buckets; +} + +/** + * @brief Query the number of buckets used in @p map + * + * @param map Hashmap to query + * @return Number of buckets used in @p map + */ +static inline size_t sys_hashmap_num_buckets(const struct sys_hashmap *map) +{ + return map->data->n_buckets; +} + +/** + * @brief Decide whether the Hashmap should be resized + * + * This is a simple opportunistic method that implementations + * can choose to use. It will grow and shrink the Hashmap by a factor + * of 2 when insertion / removal would exceed / fall into the specified + * load factor. + * + * @note Users should call this prior to inserting a new key-value pair and after removing a + * key-value pair. + * + * @note The number of reserved entries is implementation-defined, but it is only considered + * as part of the load factor when growing the hash table. + * + * @param map Hashmap to examine + * @param grow true if an entry is to be added. false if an entry has been removed + * @param num_reserved the number of reserved entries + * @param[out] new_num_buckets variable Hashmap size + * @return true if the Hashmap should be rehashed + * @return false if the Hashmap should not be rehashed + */ +static inline bool sys_hashmap_should_rehash(const struct sys_hashmap *map, bool grow, + size_t num_reserved, size_t *new_num_buckets) +{ + size_t size; + bool should_grow; + size_t n_buckets; + bool should_shrink; + const bool shrink = !grow; + struct sys_hashmap_oa_lp_data *const data = (struct sys_hashmap_oa_lp_data *)map->data; + const struct sys_hashmap_config *const config = map->config; + + /* All branchless calculations, so very cache-friendly */ + + /* calculate new size */ + size = data->size; + size += grow; + /* maximum size imposed by the implementation */ + __ASSERT_NO_MSG(size < SIZE_MAX / 100); + + /* calculate new number of buckets */ + n_buckets = data->n_buckets; + /* initial number of buckets */ + n_buckets += grow * (size == 1) * config->initial_n_buckets; + /* grow at a rate of 2x */ + n_buckets <<= grow * (size != 1); + /* shrink at a rate of 2x */ + n_buckets >>= shrink; + + /* shrink to zero if empty */ + n_buckets *= (size != 0); + + __ASSERT_NO_MSG(new_num_buckets != NULL); + __ASSERT_NO_MSG(new_num_buckets != &data->n_buckets); + *new_num_buckets = n_buckets; + + should_grow = + grow && (data->n_buckets == 0 || + (size + num_reserved) * 100 / data->n_buckets > map->config->load_factor); + should_shrink = + shrink && (n_buckets == 0 || (size * 100) / n_buckets <= map->config->load_factor); + + return should_grow || should_shrink; +} + +/** @} */ + +#ifdef __cplusplus +} +#endif + +#endif /* ZEPHYR_INCLUDE_SYS_HASH_MAP_H_ */ diff --git a/include/zephyr/sys/hash_map_api.h b/include/zephyr/sys/hash_map_api.h new file mode 100644 index 0000000000..7b6ef43532 --- /dev/null +++ b/include/zephyr/sys/hash_map_api.h @@ -0,0 +1,242 @@ +/* + * Copyright (c) 2022 Meta + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * @file + * @brief Hashmap (Hash Table) API + * + * Hashmaps (a.k.a Hash Tables) sacrifice space for speed. All operations + * on a Hashmap (insert, delete, search) are O(1) complexity (on average). + */ + +#ifndef ZEPHYR_INCLUDE_SYS_HASHMAP_API_H_ +#define ZEPHYR_INCLUDE_SYS_HASHMAP_API_H_ + +#include +#include +#include + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @defgroup hashmap_apis Hashmap + * @ingroup datastructure_apis + * @{ + */ + +/** + * @brief Generic Hashmap iterator interface + * + * @note @a next should not be used without first checking + * @ref sys_hashmap_iterator_has_next + * + * @param map Pointer to the associated Hashmap + * @param next Modify the iterator in-place to point to the next Hashmap entry + * @param state Implementation-specific iterator state + * @param key Key associated with the current entry + * @param value Value associated with the current entry + * @param size Number of entries in the map + * @param pos Number of entries already iterated + */ +struct sys_hashmap_iterator { + const struct sys_hashmap *map; + void (*next)(struct sys_hashmap_iterator *it); + void *state; + uint64_t key; + uint64_t value; + const size_t size; + size_t pos; +}; + +/** + * @brief Check if a Hashmap iterator has a next entry + * + * @param it Hashmap iterator + * @return true if there is a next entry + * @return false if there is no next entry + */ +static inline bool sys_hashmap_iterator_has_next(const struct sys_hashmap_iterator *it) +{ + return it->pos < it->size; +} + +/** + * @brief Allocator interface for @ref sys_hashmap + * + * The Hashmap allocator can be any allocator that behaves similarly to `realloc()` with the + * additional specification that the allocator behaves like `free()` when @p new_size is zero. + * + * @param ptr Previously allocated memory region or `NULL` to make a new vallocation. + * @param new_size the new size of the allocation, in bytes. + * + * @see realloc + */ +typedef void *(*sys_hashmap_allocator_t)(void *ptr, size_t new_size); + +/** + * @brief In-place iterator constructor for @ref sys_hashmap + * + * Construct an iterator, @p it, for @p map. + * + * @param map Hashmap to iterate over. + * @param it Iterator to initialize. + */ +typedef void (*sys_hashmap_iterator_t)(const struct sys_hashmap *map, + struct sys_hashmap_iterator *it); + +/** + * @brief Callback interface for @ref sys_hashmap + * + * This callback is used by some Hashmap methods. + * + * @param key Key corresponding to @p value + * @param value Value corresponding to @p key + * @param cookie User-specified variable + */ +typedef void (*sys_hashmap_callback_t)(uint64_t key, uint64_t value, void *cookie); + +/** + * @brief Clear all entries contained in a @ref sys_hashmap + * + * @note If the values in a particular Hashmap are + * + * @param map Hashmap to clear + * @param cb Callback to call for each entry + * @param cookie User-specified variable + */ +typedef void (*sys_hashmap_clear_t)(struct sys_hashmap *map, sys_hashmap_callback_t cb, + void *cookie); + +/** + * @brief Insert a new entry into a @ref sys_hashmap + * + * Insert a new @p key - @p value pair into @p map. + * + * @param map Hashmap to insert into + * @param key Key to associate with @p value + * @param value Value to associate with @p key + * @param old_value Location to store the value previously associated with @p key or `NULL` + * @retval 0 if @p value was inserted for an existing key, in which case @p old_value will contain + * the previous value + * @retval 1 if a new entry was inserted for the @p key - @p value pair + * @retval -ENOMEM if memory allocation failed + */ +typedef int (*sys_hashmap_insert_t)(struct sys_hashmap *map, uint64_t key, uint64_t value, + uint64_t *old_value); + +/** + * @brief Remove an entry from a @ref sys_hashmap + * + * Erase the entry associated with key @p key, if one exists. + * + * @param map Hashmap to remove from + * @param key Key to remove from @p map + * @param value Location to store a potential value associated with @p key or `NULL` + * + * @retval true if @p map was modified as a result of this operation. + * @retval false if @p map does not contain a value associated with @p key. + */ +typedef bool (*sys_hashmap_remove_t)(struct sys_hashmap *map, uint64_t key, uint64_t *value); + +/** + * @brief Get a value from a @ref sys_hashmap + * + * Look-up the @ref uint64_t associated with @p key, if one exists. + * + * @param map Hashmap to search through + * @param key Key with which to search @p map + * @param value Location to store a potential value associated with @p key or `NULL` + * + * @retval true if @p map contains a value associated with @p key. + * @retval false if @p map does not contain a value associated with @p key. + */ +typedef bool (*sys_hashmap_get_t)(const struct sys_hashmap *map, uint64_t key, uint64_t *value); + +/** + * @brief Generic Hashmap API + * + * @param iter Iterator constructor (in-place) + * @param next Forward-incrementer for iterator + * @param clear Clear the hash table, freeing all resources + * @param insert Insert a key-value pair into the Hashmap + * @param remove Remove a key-value pair from the Hashmap + * @param get Retrieve a the value associated with a given key from the Hashmap + */ +struct sys_hashmap_api { + sys_hashmap_iterator_t iter; + sys_hashmap_clear_t clear; + sys_hashmap_insert_t insert; + sys_hashmap_remove_t remove; + sys_hashmap_get_t get; +}; + +/** + * @brief Generic Hashmap configuration + * + * When there is a known limit imposed on the number of entries in the Hashmap, + * users should specify that via @a max_size. When the Hashmap should have + * no artificial limitation in size (and be bounded only by the provided + * allocator), users should specify `SIZE_MAX` here. + * + * The @a load_factor is defined as the size of the Hashmap divided by the + * number of buckets. In this case, the size of the Hashmap is defined as + * the number of valid entries plus the number of invalidated entries. + * + * The @a initial_n_buckets is defined as the number of buckets to allocate + * when moving from size 0 to size 1 such that the maximum @a load_factor + * property is preserved. + * + * @param max_size Maximum number of entries + * @param load_factor Maximum load factor of expressed in hundredths + * @param initial_n_buckets Initial number of buckets to allocate + */ +struct sys_hashmap_config { + size_t max_size; + uint8_t load_factor; + uint8_t initial_n_buckets; +}; + +/** + * @brief Initializer for @p sys_hashmap_config + * + * This macro helps to initialize a structure of type @p sys_hashmap_config. + * + * @param _max_size Maximum number of entries + * @param _load_factor Maximum load factor of expressed in hundredths + */ +#define SYS_HASHMAP_CONFIG(_max_size, _load_factor) \ + { \ + .max_size = (size_t)_max_size, .load_factor = (uint8_t)_load_factor, \ + .initial_n_buckets = NHPOT(ceiling_fraction(100, _load_factor)), \ + } + +/** + * @brief Generic Hashmap data + * + * @note When @a size is zero, @a buckets should be `NULL`. + * + * @param buckets Pointer for implementation-specific Hashmap storage + * @param n_buckets The number of buckets currently allocated + * @param size The number of entries currently in the Hashmap + */ +struct sys_hashmap_data { + void *buckets; + size_t n_buckets; + size_t size; +}; + +/** @} */ + +#ifdef __cplusplus +} +#endif + +#endif /* ZEPHYR_INCLUDE_SYS_HASHMAP_API_H_ */ diff --git a/include/zephyr/sys/hash_map_cxx.h b/include/zephyr/sys/hash_map_cxx.h new file mode 100644 index 0000000000..cbb6e1e591 --- /dev/null +++ b/include/zephyr/sys/hash_map_cxx.h @@ -0,0 +1,102 @@ +/* + * Copyright (c) 2022 Meta + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * @file + * @brief C++ Hashmap + * + * This is a C wrapper around `std::unordered_map`. It is mainly used for + * benchmarking purposes. + * + * @note Enable with @kconfig{CONFIG_SYS_HASH_MAP_CXX} + */ + +#ifndef ZEPHYR_INCLUDE_SYS_HASH_MAP_CXX_H_ +#define ZEPHYR_INCLUDE_SYS_HASH_MAP_CXX_H_ + +#include + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Declare a C++ Hashmap (advanced) + * + * Declare a C++ Hashmap with control over advanced parameters. + * + * @note The allocator @p _alloc is used for allocating internal Hashmap + * entries and does not interact with any user-provided keys or values. + * + * @param _name Name of the Hashmap. + * @param _hash_func Hash function pointer of type @ref sys_hash_func32_t. + * @param _alloc_func Allocator function pointer of type @ref sys_hashmap_allocator_t. + * @param ... Variant-specific details for @ref sys_hashmap_config. + */ +#define SYS_HASHMAP_CXX_DEFINE_ADVANCED(_name, _hash_func, _alloc_func, ...) \ + SYS_HASHMAP_DEFINE_ADVANCED(_name, &sys_hashmap_cxx_api, sys_hashmap_config, \ + sys_hashmap_data, _hash_func, _alloc_func, __VA_ARGS__) + +/** + * @brief Declare a C++ Hashmap (advanced) + * + * Declare a C++ Hashmap with control over advanced parameters. + * + * @note The allocator @p _alloc is used for allocating internal Hashmap + * entries and does not interact with any user-provided keys or values. + * + * @param _name Name of the Hashmap. + * @param _hash_func Hash function pointer of type @ref sys_hash_func32_t. + * @param _alloc_func Allocator function pointer of type @ref sys_hashmap_allocator_t. + * @param ... Details for @ref sys_hashmap_config. + */ +#define SYS_HASHMAP_CXX_DEFINE_STATIC_ADVANCED(_name, _hash_func, _alloc_func, ...) \ + SYS_HASHMAP_DEFINE_STATIC_ADVANCED(_name, &sys_hashmap_cxx_api, sys_hashmap_config, \ + sys_hashmap_data, _hash_func, _alloc_func, __VA_ARGS__) + +/** + * @brief Declare a C++ Hashmap statically + * + * Declare a C++ Hashmap statically with default parameters. + * + * @param _name Name of the Hashmap. + */ +#define SYS_HASHMAP_CXX_DEFINE_STATIC(_name) \ + SYS_HASHMAP_CXX_DEFINE_STATIC_ADVANCED( \ + _name, sys_hash32, SYS_HASHMAP_DEFAULT_ALLOCATOR, \ + SYS_HASHMAP_CONFIG(SIZE_MAX, SYS_HASHMAP_DEFAULT_LOAD_FACTOR)) + +/** + * @brief Declare a C++ Hashmap + * + * Declare a C++ Hashmap with default parameters. + * + * @param _name Name of the Hashmap. + */ +#define SYS_HASHMAP_CXX_DEFINE(_name) \ + SYS_HASHMAP_CXX_DEFINE_ADVANCED( \ + _name, sys_hash32, SYS_HASHMAP_DEFAULT_ALLOCATOR, \ + SYS_HASHMAP_CONFIG(SIZE_MAX, SYS_HASHMAP_DEFAULT_LOAD_FACTOR)) + +#ifdef CONFIG_SYS_HASH_MAP_CHOICE_CXX +#define SYS_HASHMAP_DEFAULT_DEFINE(_name) SYS_HASHMAP_CXX_DEFINE(_name) +#define SYS_HASHMAP_DEFAULT_DEFINE_STATIC(_name) SYS_HASHMAP_CXX_DEFINE_STATIC(_name) +#define SYS_HASHMAP_DEFAULT_DEFINE_ADVANCED(_name, _hash_func, _alloc_func, ...) \ + SYS_HASHMAP_CXX_DEFINE_ADVANCED(_name, _hash_func, _alloc_func, __VA_ARGS__) +#define SYS_HASHMAP_DEFAULT_DEFINE_STATIC_ADVANCED(_name, _hash_func, _alloc_func, ...) \ + SYS_HASHMAP_CXX_DEFINE_STATIC_ADVANCED(_name, _hash_func, _alloc_func, __VA_ARGS__) +#endif + +extern const struct sys_hashmap_api sys_hashmap_cxx_api; + +#ifdef __cplusplus +} +#endif + +#endif /* ZEPHYR_INCLUDE_SYS_HASH_MAP_CXX_H_ */ diff --git a/include/zephyr/sys/hash_map_oa_lp.h b/include/zephyr/sys/hash_map_oa_lp.h new file mode 100644 index 0000000000..0532ead95a --- /dev/null +++ b/include/zephyr/sys/hash_map_oa_lp.h @@ -0,0 +1,107 @@ +/* + * Copyright (c) 2022 Meta + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * @file + * @brief Open-Addressing / Linear Probe Hashmap Implementation + * + * @note Enable with @kconfig{CONFIG_SYS_HASH_MAP_OA_LP} + */ + +#ifndef ZEPHYR_INCLUDE_SYS_HASH_MAP_OA_LP_H_ +#define ZEPHYR_INCLUDE_SYS_HASH_MAP_OA_LP_H_ + +#include + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +struct sys_hashmap_oa_lp_data { + void *buckets; + size_t n_buckets; + size_t size; + size_t n_tombstones; +}; + +/** + * @brief Declare a Open Addressing Linear Probe Hashmap (advanced) + * + * Declare a Open Addressing Linear Probe Hashmap with control over advanced parameters. + * + * @note The allocator @p _alloc is used for allocating internal Hashmap + * entries and does not interact with any user-provided keys or values. + * + * @param _name Name of the Hashmap. + * @param _hash_func Hash function pointer of type @ref sys_hash_func32_t. + * @param _alloc_func Allocator function pointer of type @ref sys_hashmap_allocator_t. + * @param ... Variant-specific details for @ref sys_hashmap_config. + */ +#define SYS_HASHMAP_OA_LP_DEFINE_ADVANCED(_name, _hash_func, _alloc_func, ...) \ + SYS_HASHMAP_DEFINE_ADVANCED(_name, &sys_hashmap_oa_lp_api, sys_hashmap_config, \ + sys_hashmap_oa_lp_data, _hash_func, _alloc_func, __VA_ARGS__) + +/** + * @brief Declare a Open Addressing Linear Probe Hashmap (advanced) + * + * Declare a Open Addressing Linear Probe Hashmap with control over advanced parameters. + * + * @note The allocator @p _alloc is used for allocating internal Hashmap + * entries and does not interact with any user-provided keys or values. + * + * @param _name Name of the Hashmap. + * @param _hash_func Hash function pointer of type @ref sys_hash_func32_t. + * @param _alloc_func Allocator function pointer of type @ref sys_hashmap_allocator_t. + * @param ... Details for @ref sys_hashmap_config. + */ +#define SYS_HASHMAP_OA_LP_DEFINE_STATIC_ADVANCED(_name, _hash_func, _alloc_func, ...) \ + SYS_HASHMAP_DEFINE_STATIC_ADVANCED(_name, &sys_hashmap_oa_lp_api, sys_hashmap_config, \ + sys_hashmap_oa_lp_data, _hash_func, _alloc_func, \ + __VA_ARGS__) + +/** + * @brief Declare a Open Addressing Linear Probe Hashmap statically + * + * Declare a Open Addressing Linear Probe Hashmap statically with default parameters. + * + * @param _name Name of the Hashmap. + */ +#define SYS_HASHMAP_OA_LP_DEFINE_STATIC(_name) \ + SYS_HASHMAP_OA_LP_DEFINE_STATIC_ADVANCED( \ + _name, sys_hash32, SYS_HASHMAP_DEFAULT_ALLOCATOR, \ + SYS_HASHMAP_CONFIG(SIZE_MAX, SYS_HASHMAP_DEFAULT_LOAD_FACTOR)) + +/** + * @brief Declare a Open Addressing Linear Probe Hashmap + * + * Declare a Open Addressing Linear Probe Hashmap with default parameters. + * + * @param _name Name of the Hashmap. + */ +#define SYS_HASHMAP_OA_LP_DEFINE(_name) \ + SYS_HASHMAP_OA_LP_DEFINE_ADVANCED( \ + _name, sys_hash32, SYS_HASHMAP_DEFAULT_ALLOCATOR, \ + SYS_HASHMAP_CONFIG(SIZE_MAX, SYS_HASHMAP_DEFAULT_LOAD_FACTOR)) + +#ifdef CONFIG_SYS_HASH_MAP_CHOICE_OA_LP +#define SYS_HASHMAP_DEFAULT_DEFINE(_name) SYS_HASHMAP_OA_LP_DEFINE(_name) +#define SYS_HASHMAP_DEFAULT_DEFINE_STATIC(_name) SYS_HASHMAP_OA_LP_DEFINE_STATIC(_name) +#define SYS_HASHMAP_DEFAULT_DEFINE_ADVANCED(_name, _hash_func, _alloc_func, ...) \ + SYS_HASHMAP_OA_LP_DEFINE_ADVANCED(_name, _hash_func, _alloc_func, __VA_ARGS__) +#define SYS_HASHMAP_DEFAULT_DEFINE_STATIC_ADVANCED(_name, _hash_func, _alloc_func, ...) \ + SYS_HASHMAP_OA_LP_DEFINE_STATIC_ADVANCED(_name, _hash_func, _alloc_func, __VA_ARGS__) +#endif + +extern const struct sys_hashmap_api sys_hashmap_oa_lp_api; + +#ifdef __cplusplus +} +#endif + +#endif /* ZEPHYR_INCLUDE_SYS_HASH_MAP_OA_LP_H_ */ diff --git a/include/zephyr/sys/hash_map_sc.h b/include/zephyr/sys/hash_map_sc.h new file mode 100644 index 0000000000..9ee1ea6a13 --- /dev/null +++ b/include/zephyr/sys/hash_map_sc.h @@ -0,0 +1,102 @@ +/* + * Copyright (c) 2022 Meta + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * @file + * @brief Separate Chaining Hashmap Implementation + * + * @note Enable with @kconfig{CONFIG_SYS_HASH_MAP_SC} + */ + +#ifndef ZEPHYR_INCLUDE_SYS_HASH_MAP_SC_H_ +#define ZEPHYR_INCLUDE_SYS_HASH_MAP_SC_H_ + +#include +#include +#include +#include + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Declare a Separate Chaining Hashmap (advanced) + * + * Declare a Separate Chaining Hashmap with control over advanced parameters. + * + * @note The allocator @p _alloc_func is used for allocating internal Hashmap + * entries and does not interact with any user-provided keys or values. + * + * @param _name Name of the Hashmap. + * @param _hash_func Hash function pointer of type @ref sys_hash_func32_t. + * @param _alloc_func Allocator function pointer of type @ref sys_hashmap_allocator_t. + * @param ... Details for @ref sys_hashmap_config. + */ +#define SYS_HASHMAP_SC_DEFINE_ADVANCED(_name, _hash_func, _alloc_func, ...) \ + SYS_HASHMAP_DEFINE_ADVANCED(_name, &sys_hashmap_sc_api, sys_hashmap_config, \ + sys_hashmap_data, _hash_func, _alloc_func, __VA_ARGS__) + +/** + * @brief Declare a Separate Chaining Hashmap (advanced) + * + * Declare a Separate Chaining Hashmap with control over advanced parameters. + * + * @note The allocator @p _alloc is used for allocating internal Hashmap + * entries and does not interact with any user-provided keys or values. + * + * @param _name Name of the Hashmap. + * @param _hash_func Hash function pointer of type @ref sys_hash_func32_t. + * @param _alloc_func Allocator function pointer of type @ref sys_hashmap_allocator_t. + * @param ... Details for @ref sys_hashmap_config. + */ +#define SYS_HASHMAP_SC_DEFINE_STATIC_ADVANCED(_name, _hash_func, _alloc_func, ...) \ + SYS_HASHMAP_DEFINE_STATIC_ADVANCED(_name, &sys_hashmap_sc_api, sys_hashmap_config, \ + sys_hashmap_data, _hash_func, _alloc_func, __VA_ARGS__) + +/** + * @brief Declare a Separate Chaining Hashmap statically + * + * Declare a Separate Chaining Hashmap statically with default parameters. + * + * @param _name Name of the Hashmap. + */ +#define SYS_HASHMAP_SC_DEFINE_STATIC(_name) \ + SYS_HASHMAP_SC_DEFINE_ADVANCED( \ + _name, sys_hash32, SYS_HASHMAP_DEFAULT_ALLOCATOR, \ + SYS_HASHMAP_CONFIG(SIZE_MAX, SYS_HASHMAP_DEFAULT_LOAD_FACTOR)) + +/** + * @brief Declare a Separate Chaining Hashmap + * + * Declare a Separate Chaining Hashmap with default parameters. + * + * @param _name Name of the Hashmap. + */ +#define SYS_HASHMAP_SC_DEFINE(_name) \ + SYS_HASHMAP_SC_DEFINE_ADVANCED( \ + _name, sys_hash32, SYS_HASHMAP_DEFAULT_ALLOCATOR, \ + SYS_HASHMAP_CONFIG(SIZE_MAX, SYS_HASHMAP_DEFAULT_LOAD_FACTOR)) + +#ifdef CONFIG_SYS_HASH_MAP_CHOICE_SC +#define SYS_HASHMAP_DEFAULT_DEFINE(_name) SYS_HASHMAP_SC_DEFINE(_name) +#define SYS_HASHMAP_DEFAULT_DEFINE_STATIC(_name) SYS_HASHMAP_SC_DEFINE_STATIC(_name) +#define SYS_HASHMAP_DEFAULT_DEFINE_ADVANCED(_name, _hash_func, _alloc_func, ...) \ + SYS_HASHMAP_SC_DEFINE_ADVANCED(_name, _hash_func, _alloc_func, __VA_ARGS__) +#define SYS_HASHMAP_DEFAULT_DEFINE_STATIC_ADVANCED(_name, _hash_func, _alloc_func, ...) \ + SYS_HASHMAP_SC_DEFINE_STATIC_ADVANCED(_name, _hash_func, _alloc_func, __VA_ARGS__) +#endif + +extern const struct sys_hashmap_api sys_hashmap_sc_api; + +#ifdef __cplusplus +} +#endif + +#endif /* ZEPHYR_INCLUDE_SYS_HASH_MAP_SC_H_ */ diff --git a/lib/os/CMakeLists.txt b/lib/os/CMakeLists.txt index f53ac42975..f0f9c490dc 100644 --- a/lib/os/CMakeLists.txt +++ b/lib/os/CMakeLists.txt @@ -67,6 +67,10 @@ zephyr_sources_ifdef(CONFIG_WINSTREAM winstream.c) zephyr_sources_ifdef(CONFIG_SYS_HASH_FUNC32_DJB2 hash_func32_djb2.c) zephyr_sources_ifdef(CONFIG_SYS_HASH_FUNC32_MURMUR3 hash_func32_murmur3.c) +zephyr_sources_ifdef(CONFIG_SYS_HASH_MAP_SC hash_map_sc.c) +zephyr_sources_ifdef(CONFIG_SYS_HASH_MAP_OA_LP hash_map_oa_lp.c) +zephyr_sources_ifdef(CONFIG_SYS_HASH_MAP_CXX hash_map_cxx.cpp) + zephyr_library_include_directories( ${ZEPHYR_BASE}/kernel/include ${ZEPHYR_BASE}/arch/${ARCH}/include diff --git a/lib/os/Kconfig b/lib/os/Kconfig index 43538ea1b4..e209566124 100644 --- a/lib/os/Kconfig +++ b/lib/os/Kconfig @@ -177,5 +177,6 @@ rsource "Kconfig.cbprintf" rsource "Kconfig.heap" rsource "Kconfig.hash_func" +rsource "Kconfig.hash_map" endmenu diff --git a/lib/os/Kconfig.hash_map b/lib/os/Kconfig.hash_map new file mode 100644 index 0000000000..dfbe9e8a82 --- /dev/null +++ b/lib/os/Kconfig.hash_map @@ -0,0 +1,70 @@ +# Copyright (c) 2022 Meta +# +# SPDX-License-Identifier: Apache-2.0 + +menu "Hashmap (Hash Table) Support" + +config SYS_HASH_MAP + bool "Hashmap support" + help + Support for Hashmaps (a.k.a. Hash Tables). + + Hashmaps are data structures that are used when insertion, removal, and + lookup of key-value pairs must be done in O(1) time (on average). + +if SYS_HASH_MAP + +config SYS_HASH_MAP_SC + bool "Separate-Chaining Hashmap" + help + Separate-Chaining Hashmaps implement each bucket as a linked-list. + + They are perhaps more useful on resource-constrained systems where + large contiguous memory regions are scarce. + + The main disadvantage of Separate-Chaining Hashmaps are that their + use tends to incur more cache-misses as nodes are spread throughout + the heap. + +config SYS_HASH_MAP_OA_LP + bool "Open-Addressing / Linear Probe Hashmap" + help + Open-Addressing Hashmaps do not chain entries together but instead + store all entries in the table itself which is a contiguously allocated + memory region. + + The main advantage of Open-Addressing Hashmaps are due to their + contiguous allocation which improves performance on systems with + memory caching. + +config SYS_HASH_MAP_CXX + bool "C++ Hashmap" + select CPLUSPLUS + select LIB_CPLUSPLUS + select EXCEPTIONS + help + This enables a C wrapper around the C++ std::unordered_map. + + It is mainly used for benchmarking purposes. + +choice SYS_HASH_MAP_CHOICE + prompt "Default hashmap implementation" + default SYS_HASH_MAP_CHOICE_SC + +config SYS_HASH_MAP_CHOICE_SC + bool "Default hash is Separate-Chaining" + select SYS_HASH_MAP_SC + +config SYS_HASH_MAP_CHOICE_OA_LP + bool "Default hash is Open-Addressing / Linear Probe" + select SYS_HASH_MAP_OA_LP + +config SYS_HASH_MAP_CHOICE_CXX + bool "Default hash is C++" + select SYS_HASH_MAP_CXX + +endchoice # SYS_HASH_MAP_CHOICE + +endif + +endmenu diff --git a/lib/os/hash_map_cxx.cpp b/lib/os/hash_map_cxx.cpp new file mode 100644 index 0000000000..e8b2806071 --- /dev/null +++ b/lib/os/hash_map_cxx.cpp @@ -0,0 +1,157 @@ +/* + * Copyright (c) 2022 Meta + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include + +#include +#include + +using cxx_map = std::unordered_map; + +static void sys_hashmap_cxx_iter_next(struct sys_hashmap_iterator *it) +{ + cxx_map *umap = static_cast(it->map->data->buckets); + + __ASSERT_NO_MSG(umap != nullptr); + + __ASSERT(it->size == it->map->data->size, "Concurrent modification!"); + __ASSERT(sys_hashmap_iterator_has_next(it), "Attempt to access beyond current bound!"); + + auto it2 = umap->begin(); + for (size_t i = 0; i < it->pos; ++i, it2++) + ; + + it->key = it2->first; + it->value = it2->second; + ++it->pos; +} + +/* + * C++ Wrapped Hashmap API + */ + +static void sys_hashmap_cxx_iter(const struct sys_hashmap *map, struct sys_hashmap_iterator *it) +{ + it->map = map; + it->next = sys_hashmap_cxx_iter_next; + it->state = nullptr; + it->key = 0; + it->value = 0; + it->pos = 0; + *((size_t *)&it->size) = map->data->size; +} + +static void sys_hashmap_cxx_clear(struct sys_hashmap *map, sys_hashmap_callback_t cb, void *cookie) +{ + cxx_map *umap = static_cast(map->data->buckets); + + if (umap == nullptr) { + return; + } + + if (cb != nullptr) { + for (auto &kv : *umap) { + cb(kv.first, kv.second, cookie); + } + } + + delete umap; + + map->data->buckets = nullptr; + map->data->n_buckets = 0; + map->data->size = 0; +} + +static int sys_hashmap_cxx_insert(struct sys_hashmap *map, uint64_t key, uint64_t value, + uint64_t *old_value) +{ + cxx_map *umap = static_cast(map->data->buckets); + + if (umap == nullptr) { + umap = new cxx_map; + umap->max_load_factor(map->config->load_factor / 100.0f); + map->data->buckets = umap; + } + + auto it = umap->find(key); + if (it != umap->end() && old_value != nullptr) { + *old_value = it->second; + it->second = value; + return 0; + } + + try { + (*umap)[key] = value; + } catch(...) { + return -ENOMEM; + } + + ++map->data->size; + map->data->n_buckets = umap->bucket_count(); + + return 1; +} + +static bool sys_hashmap_cxx_remove(struct sys_hashmap *map, uint64_t key, uint64_t *value) +{ + cxx_map *umap = static_cast(map->data->buckets); + + if (umap == nullptr) { + return false; + } + + auto it = umap->find(key); + if (it == umap->end()) { + return false; + } + + if (value != nullptr) { + *value = it->second; + } + + umap->erase(key); + --map->data->size; + map->data->n_buckets = umap->bucket_count(); + + if (map->data->size == 0) { + delete umap; + map->data->n_buckets = 0; + map->data->buckets = nullptr; + } + + return true; +} + +static bool sys_hashmap_cxx_get(const struct sys_hashmap *map, uint64_t key, uint64_t *value) +{ + cxx_map *umap = static_cast(map->data->buckets); + + if (umap == nullptr) { + return false; + } + + auto it = umap->find(key); + if (it == umap->end()) { + return false; + } + + if (value != nullptr) { + *value = it->second; + } + + return true; +} + +extern "C" { +const struct sys_hashmap_api sys_hashmap_cxx_api = { + .iter = sys_hashmap_cxx_iter, + .clear = sys_hashmap_cxx_clear, + .insert = sys_hashmap_cxx_insert, + .remove = sys_hashmap_cxx_remove, + .get = sys_hashmap_cxx_get, +}; +} diff --git a/lib/os/hash_map_oa_lp.c b/lib/os/hash_map_oa_lp.c new file mode 100644 index 0000000000..e40112dfad --- /dev/null +++ b/lib/os/hash_map_oa_lp.c @@ -0,0 +1,293 @@ +/* + * Copyright (c) 2022 Meta + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +enum bucket_state { + UNUSED, + USED, + TOMBSTONE, +}; + +struct oalp_entry { + uint64_t key; + uint64_t value; + enum bucket_state state; +}; + +BUILD_ASSERT(offsetof(struct sys_hashmap_oa_lp_data, buckets) == + offsetof(struct sys_hashmap_data, buckets)); +BUILD_ASSERT(offsetof(struct sys_hashmap_oa_lp_data, n_buckets) == + offsetof(struct sys_hashmap_data, n_buckets)); +BUILD_ASSERT(offsetof(struct sys_hashmap_oa_lp_data, size) == + offsetof(struct sys_hashmap_data, size)); + +static struct oalp_entry *sys_hashmap_oa_lp_find(const struct sys_hashmap *map, uint64_t key, + bool used_ok, bool unused_ok, bool tombstone_ok) +{ + struct oalp_entry *entry = NULL; + const size_t n_buckets = map->data->n_buckets; + uint32_t hash = map->hash_func(&key, sizeof(key)); + struct oalp_entry *const buckets = map->data->buckets; + + for (size_t i = 0, j = hash; i < n_buckets; ++i, ++j) { + j &= (n_buckets - 1); + __ASSERT_NO_MSG(j < n_buckets); + + entry = &buckets[j]; + + switch (entry->state) { + case USED: + if (used_ok && entry->key == key) { + return entry; + } + break; + case UNUSED: + if (unused_ok) { + return entry; + } + break; + case TOMBSTONE: + if (tombstone_ok) { + return entry; + } + break; + default: + __ASSERT(false, "Invalid entry state. Memory has been corrupted"); + break; + } + } + + return NULL; +} + +static int sys_hashmap_oa_lp_insert_no_rehash(struct sys_hashmap *map, uint64_t key, uint64_t value, + uint64_t *old_value) +{ + int ret; + struct oalp_entry *entry = NULL; + struct sys_hashmap_oa_lp_data *data = (struct sys_hashmap_oa_lp_data *)map->data; + + entry = sys_hashmap_oa_lp_find(map, key, true, true, true); + __ASSERT_NO_MSG(entry != NULL); + + switch (entry->state) { + case UNUSED: + ++data->size; + ret = 1; + break; + case TOMBSTONE: + --data->n_tombstones; + ++data->size; + ret = 0; + break; + case USED: + default: + ret = 0; + break; + } + + if (old_value != NULL) { + *old_value = entry->value; + } + + entry->state = USED; + entry->key = key; + entry->value = value; + + return ret; +} + +static int sys_hashmap_oa_lp_rehash(struct sys_hashmap *map, bool grow) +{ + size_t old_size; + size_t old_n_buckets; + size_t new_n_buckets = 0; + struct oalp_entry *entry; + struct oalp_entry *old_buckets; + struct oalp_entry *new_buckets; + struct sys_hashmap_oa_lp_data *data = (struct sys_hashmap_oa_lp_data *)map->data; + + if (!sys_hashmap_should_rehash(map, grow, data->n_tombstones, &new_n_buckets)) { + return 0; + } + + if (map->data->size != SIZE_MAX && map->data->size == map->config->max_size) { + return -ENOSPC; + } + + /* extract all entries from the hashmap */ + old_size = data->size; + old_n_buckets = data->n_buckets; + old_buckets = (struct oalp_entry *)data->buckets; + + new_buckets = (struct oalp_entry *)map->alloc_func(NULL, new_n_buckets * sizeof(*entry)); + if (new_buckets == NULL && new_n_buckets != 0) { + return -ENOMEM; + } + + if (new_buckets != NULL) { + /* ensure all buckets are empty / initialized */ + memset(new_buckets, 0, new_n_buckets * sizeof(*new_buckets)); + } + + data->size = 0; + data->buckets = new_buckets; + data->n_buckets = new_n_buckets; + + /* re-insert all entries into the hashmap */ + for (size_t i = 0, j = 0; i < old_n_buckets && j < old_size; ++i) { + entry = &old_buckets[i]; + + if (entry->state == USED) { + sys_hashmap_oa_lp_insert_no_rehash(map, entry->key, entry->value, NULL); + ++j; + } + } + + /* free the old Hashmap */ + map->alloc_func(old_buckets, 0); + + return 0; +} + +static void sys_hashmap_oa_lp_iter_next(struct sys_hashmap_iterator *it) +{ + size_t i; + struct oalp_entry *entry; + const struct sys_hashmap *map = (const struct sys_hashmap *)it->map; + struct oalp_entry *buckets = map->data->buckets; + + __ASSERT(it->size == map->data->size, "Concurrent modification!"); + __ASSERT(sys_hashmap_iterator_has_next(it), "Attempt to access beyond current bound!"); + + if (it->pos == 0) { + it->state = buckets; + } + + i = (struct oalp_entry *)it->state - buckets; + __ASSERT(i < map->data->n_buckets, "Invalid iterator state %p", it->state); + + for (; i < map->data->n_buckets; ++i) { + entry = &buckets[i]; + if (entry->state == USED) { + it->state = &buckets[i + 1]; + it->key = entry->key; + it->value = entry->value; + ++it->pos; + return; + } + } + + __ASSERT(false, "Entire Hashmap traversed and no entry was found"); +} + +/* + * Open Addressing / Linear Probe Hashmap API + */ + +static void sys_hashmap_oa_lp_iter(const struct sys_hashmap *map, struct sys_hashmap_iterator *it) +{ + it->map = map; + it->next = sys_hashmap_oa_lp_iter_next; + it->pos = 0; + *((size_t *)&it->size) = map->data->size; +} + +static void sys_hashmap_oa_lp_clear(struct sys_hashmap *map, sys_hashmap_callback_t cb, + void *cookie) +{ + struct oalp_entry *entry; + struct sys_hashmap_oa_lp_data *data = (struct sys_hashmap_oa_lp_data *)map->data; + struct oalp_entry *buckets = data->buckets; + + for (size_t i = 0, j = 0; cb != NULL && i < data->n_buckets && j < data->size; ++i) { + entry = &buckets[i]; + if (entry->state == USED) { + cb(entry->key, entry->value, cookie); + ++j; + } + } + + if (data->buckets != NULL) { + map->alloc_func(data->buckets, 0); + data->buckets = NULL; + } + + data->n_buckets = 0; + data->size = 0; + data->n_tombstones = 0; +} + +static inline int sys_hashmap_oa_lp_insert(struct sys_hashmap *map, uint64_t key, uint64_t value, + uint64_t *old_value) +{ + int ret; + + ret = sys_hashmap_oa_lp_rehash(map, true); + if (ret < 0) { + return ret; + } + + return sys_hashmap_oa_lp_insert_no_rehash(map, key, value, old_value); +} + +static bool sys_hashmap_oa_lp_remove(struct sys_hashmap *map, uint64_t key, uint64_t *value) +{ + struct oalp_entry *entry; + struct sys_hashmap_oa_lp_data *data = (struct sys_hashmap_oa_lp_data *)map->data; + + entry = sys_hashmap_oa_lp_find(map, key, true, true, false); + if (entry == NULL || entry->state == UNUSED) { + return false; + } + + if (value != NULL) { + *value = entry->value; + } + + entry->state = TOMBSTONE; + --data->size; + ++data->n_tombstones; + + /* ignore a possible -ENOMEM since the table will remain intact */ + (void)sys_hashmap_oa_lp_rehash(map, false); + + return true; +} + +static bool sys_hashmap_oa_lp_get(const struct sys_hashmap *map, uint64_t key, uint64_t *value) +{ + struct oalp_entry *entry; + + entry = sys_hashmap_oa_lp_find(map, key, true, true, false); + if (entry == NULL || entry->state == UNUSED) { + return false; + } + + if (value != NULL) { + *value = entry->value; + } + + return true; +} + +const struct sys_hashmap_api sys_hashmap_oa_lp_api = { + .iter = sys_hashmap_oa_lp_iter, + .clear = sys_hashmap_oa_lp_clear, + .insert = sys_hashmap_oa_lp_insert, + .remove = sys_hashmap_oa_lp_remove, + .get = sys_hashmap_oa_lp_get, +}; diff --git a/lib/os/hash_map_sc.c b/lib/os/hash_map_sc.c new file mode 100644 index 0000000000..20d78c1115 --- /dev/null +++ b/lib/os/hash_map_sc.c @@ -0,0 +1,302 @@ +/* + * Copyright (c) 2022 Meta + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +struct sys_hashmap_sc_entry { + uint64_t key; + uint64_t value; + sys_dnode_t node; +}; + +static void sys_hashmap_sc_entry_init(struct sys_hashmap_sc_entry *entry, uint64_t key, + uint64_t value) +{ + entry->key = key; + entry->value = value; + sys_dnode_init(&entry->node); +} + +static void sys_hashmap_sc_insert_entry(struct sys_hashmap *map, struct sys_hashmap_sc_entry *entry) +{ + sys_dlist_t *buckets = map->data->buckets; + uint32_t hash = map->hash_func(&entry->key, sizeof(entry->key)); + + sys_dlist_append(&buckets[hash % map->data->n_buckets], &entry->node); + ++map->data->size; +} + +static void sys_hashmap_sc_insert_all(struct sys_hashmap *map, sys_dlist_t *list) +{ + __unused int ret; + struct sys_hashmap_sc_entry *entry; + + while (!sys_dlist_is_empty(list)) { + entry = CONTAINER_OF(sys_dlist_get(list), struct sys_hashmap_sc_entry, node); + sys_hashmap_sc_insert_entry(map, entry); + } +} + +static void sys_hashmap_sc_to_list(struct sys_hashmap *map, sys_dlist_t *list) +{ + sys_dlist_t *bucket; + struct sys_hashmap_sc_entry *entry; + sys_dlist_t *buckets = map->data->buckets; + + sys_dlist_init(list); + + for (size_t i = 0; i < map->data->n_buckets; ++i) { + bucket = &buckets[i]; + while (!sys_dlist_is_empty(bucket)) { + entry = CONTAINER_OF(sys_dlist_get(bucket), struct sys_hashmap_sc_entry, + node); + sys_dlist_append(list, &entry->node); + } + } +} + +static int sys_hashmap_sc_rehash(struct sys_hashmap *map, bool grow) +{ + sys_dlist_t list; + sys_dlist_t *bucket; + size_t new_n_buckets; + sys_dlist_t *new_buckets; + + if (!sys_hashmap_should_rehash(map, grow, 0, &new_n_buckets)) { + return 0; + } + + /* extract all entries from the hashmap */ + sys_hashmap_sc_to_list(map, &list); + + /* reallocate memory */ + new_buckets = (sys_dlist_t *)map->alloc_func(map->data->buckets, + new_n_buckets * sizeof(*new_buckets)); + if (new_buckets == NULL && new_n_buckets != 0) { + /* re-insert all entries into the hashmap if reallocation fails */ + sys_hashmap_sc_insert_all(map, &list); + return -ENOMEM; + } + + /* ensure all buckets are empty / initialized */ + map->data->size = 0; + map->data->buckets = new_buckets; + map->data->n_buckets = new_n_buckets; + for (size_t i = 0; i < new_n_buckets; ++i) { + bucket = &((sys_dlist_t *)(map->data->buckets))[i]; + sys_dlist_init(bucket); + } + + /* re-insert all entries into the hashmap */ + sys_hashmap_sc_insert_all(map, &list); + + return 0; +} + +static struct sys_hashmap_sc_entry *sys_hashmap_sc_find(const struct sys_hashmap *map, uint64_t key) +{ + uint32_t hash; + sys_dlist_t *bucket; + sys_dlist_t *buckets; + struct sys_hashmap_sc_entry *entry; + + if (map->data->n_buckets == 0) { + return NULL; + } + + __ASSERT_NO_MSG(map->data->size > 0); + + hash = map->hash_func(&key, sizeof(key)); + buckets = (sys_dlist_t *)map->data->buckets; + bucket = &buckets[hash % map->data->n_buckets]; + + SYS_DLIST_FOR_EACH_CONTAINER(bucket, entry, node) { + if (entry->key == key) { + return entry; + } + } + + return NULL; +} + +static void sys_hashmap_sc_iter_next(struct sys_hashmap_iterator *it) +{ + sys_dlist_t *bucket; + bool found_previous_key = false; + struct sys_hashmap_sc_entry *entry; + const struct sys_hashmap *map = it->map; + sys_dlist_t *buckets = map->data->buckets; + + __ASSERT(it->size == map->data->size, "Concurrent modification!"); + __ASSERT(sys_hashmap_iterator_has_next(it), "Attempt to access beyond current bound!"); + + if (it->pos == 0) { + /* at position 0, state equals the beginning of the bucket array */ + it->state = buckets; + found_previous_key = true; + } + + for (bucket = it->state; bucket < &buckets[map->data->n_buckets]; ++bucket) { + SYS_DLIST_FOR_EACH_CONTAINER(bucket, entry, node) { + if (!found_previous_key) { + if (entry->key == it->key) { + found_previous_key = true; + } + + continue; + } + + /* save the bucket to state so we can restart scanning from a saved position + */ + it->state = bucket; + it->key = entry->key; + it->value = entry->value; + ++it->pos; + + return; + } + } + + __ASSERT(false, "Entire Hashmap traversed and no entry was found"); +} + +/* + * Separate Chaining Hashmap API + */ + +static void sys_hashmap_sc_iter(const struct sys_hashmap *map, struct sys_hashmap_iterator *it) +{ + it->map = map; + it->next = sys_hashmap_sc_iter_next; + it->state = map->data->buckets; + it->key = 0; + it->value = 0; + it->pos = 0; + *((size_t *)&it->size) = map->data->size; +} + +static void sys_hashmap_sc_clear(struct sys_hashmap *map, sys_hashmap_callback_t cb, void *cookie) +{ + sys_dlist_t list; + struct sys_hashmap_sc_entry *entry; + + sys_hashmap_sc_to_list(map, &list); + + /* free the buckets */ + if (map->data->buckets != NULL) { + map->alloc_func(map->data->buckets, 0); + map->data->buckets = NULL; + } + + map->data->n_buckets = 0; + map->data->size = 0; + + while (!sys_dlist_is_empty(&list)) { + entry = CONTAINER_OF(sys_dlist_get(&list), struct sys_hashmap_sc_entry, node); + + /* call the callback for entry */ + if (cb != NULL) { + cb(entry->key, entry->value, cookie); + } + + /* free the entry using the Hashmap's allocator */ + map->alloc_func(entry, 0); + } +} + +static int sys_hashmap_sc_insert(struct sys_hashmap *map, uint64_t key, uint64_t value, + uint64_t *old_value) +{ + int ret; + struct sys_hashmap_sc_entry *entry; + + entry = sys_hashmap_sc_find(map, key); + if (entry != NULL) { + if (old_value != NULL) { + *old_value = entry->value; + } + + entry->value = value; + + return 0; + } + + ret = sys_hashmap_sc_rehash(map, true); + if (ret < 0) { + return ret; + } + + entry = map->alloc_func(NULL, sizeof(*entry)); + if (entry == NULL) { + return -ENOMEM; + } + + sys_hashmap_sc_entry_init(entry, key, value); + sys_hashmap_sc_insert_entry(map, entry); + + return 1; +} + +static bool sys_hashmap_sc_remove(struct sys_hashmap *map, uint64_t key, uint64_t *value) +{ + __unused int ret; + struct sys_hashmap_sc_entry *entry; + + entry = sys_hashmap_sc_find(map, key); + if (entry == NULL) { + return false; + } + + if (value != NULL) { + *value = entry->value; + } + + sys_dlist_remove(&entry->node); + --map->data->size; + + ret = sys_hashmap_sc_rehash(map, false); + /* Realloc to a smaller size of memory should *always* work */ + __ASSERT_NO_MSG(ret >= 0); + + /* free the entry */ + map->alloc_func(entry, 0); + + return true; +} + +static bool sys_hashmap_sc_get(const struct sys_hashmap *map, uint64_t key, uint64_t *value) +{ + struct sys_hashmap_sc_entry *entry; + + entry = sys_hashmap_sc_find(map, key); + if (entry == NULL) { + return false; + } + + if (value != NULL) { + *value = entry->value; + } + + return true; +} + +const struct sys_hashmap_api sys_hashmap_sc_api = { + .iter = sys_hashmap_sc_iter, + .clear = sys_hashmap_sc_clear, + .insert = sys_hashmap_sc_insert, + .remove = sys_hashmap_sc_remove, + .get = sys_hashmap_sc_get, +};