294 lines
6.8 KiB
C
294 lines
6.8 KiB
C
|
/*
|
||
|
* 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,
|
||
|
};
|