| /* |
| * 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, |
| }; |