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