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 <cfriedt@meta.com>
This commit is contained in:
Chris Friedt 2022-12-30 14:18:05 -05:00 committed by Carles Cufí
parent 2d4619b13c
commit 0bda7b30df
11 changed files with 1746 additions and 0 deletions

View file

@ -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 <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include <zephyr/kernel.h>
#include <zephyr/sys/hash_map_api.h>
#include <zephyr/sys/hash_map_cxx.h>
#include <zephyr/sys/hash_map_oa_lp.h>
#include <zephyr/sys/hash_map_sc.h>
#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_ */

View file

@ -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 <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include <zephyr/sys/hash_function.h>
#include <zephyr/sys/util.h>
#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 <a href="https://en.cppreference.com/w/c/memory/realloc">realloc</a>
*/
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_ */

View file

@ -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 <stddef.h>
#include <zephyr/sys/hash_function.h>
#include <zephyr/sys/hash_map_api.h>
#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_ */

View file

@ -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 <stddef.h>
#include <zephyr/sys/hash_function.h>
#include <zephyr/sys/hash_map_api.h>
#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_ */

View file

@ -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 <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include <stdlib.h>
#include <zephyr/sys/hash_function.h>
#include <zephyr/sys/hash_map_api.h>
#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_ */

View file

@ -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

View file

@ -177,5 +177,6 @@ rsource "Kconfig.cbprintf"
rsource "Kconfig.heap"
rsource "Kconfig.hash_func"
rsource "Kconfig.hash_map"
endmenu

70
lib/os/Kconfig.hash_map Normal file
View file

@ -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

157
lib/os/hash_map_cxx.cpp Normal file
View file

@ -0,0 +1,157 @@
/*
* Copyright (c) 2022 Meta
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <memory>
#include <unordered_map>
#include <zephyr/sys/hash_map.h>
#include <zephyr/sys/hash_map_cxx.h>
using cxx_map = std::unordered_map<uint64_t, uint64_t>;
static void sys_hashmap_cxx_iter_next(struct sys_hashmap_iterator *it)
{
cxx_map *umap = static_cast<cxx_map *>(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<cxx_map *>(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<cxx_map *>(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<cxx_map *>(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<cxx_map *>(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,
};
}

293
lib/os/hash_map_oa_lp.c Normal file
View file

@ -0,0 +1,293 @@
/*
* Copyright (c) 2022 Meta
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <errno.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <zephyr/sys/hash_map.h>
#include <zephyr/sys/hash_map_oa_lp.h>
#include <zephyr/sys/util.h>
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,
};

302
lib/os/hash_map_sc.c Normal file
View file

@ -0,0 +1,302 @@
/*
* Copyright (c) 2022 Meta
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <errno.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <zephyr/sys/dlist.h>
#include <zephyr/sys/hash_map.h>
#include <zephyr/sys/hash_map_sc.h>
#include <zephyr/sys/util.h>
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,
};