// SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause) /* * Generic non-thread safe hash map implementation. * * Copyright (c) 2019 Facebook */ #include <stdint.h> #include <stdlib.h> #include <stdio.h> #include <errno.h> #include <linux/err.h> #include "hashmap.h" /* make sure libbpf doesn't use kernel-only integer typedefs */ #pragma GCC poison u8 u16 u32 u64 s8 s16 s32 s64 /* prevent accidental re-addition of reallocarray() */ #pragma GCC poison reallocarray /* start with 4 buckets */ #define HASHMAP_MIN_CAP_BITS 2 static void hashmap_add_entry(struct hashmap_entry **pprev, struct hashmap_entry *entry) { entry->next = *pprev; *pprev = entry; } static void hashmap_del_entry(struct hashmap_entry **pprev, struct hashmap_entry *entry) { *pprev = entry->next; entry->next = NULL; } void hashmap__init(struct hashmap *map, hashmap_hash_fn hash_fn, hashmap_equal_fn equal_fn, void *ctx) { map->hash_fn = hash_fn; map->equal_fn = equal_fn; map->ctx = ctx; map->buckets = NULL; map->cap = 0; map->cap_bits = 0; map->sz = 0; } struct hashmap *hashmap__new(hashmap_hash_fn hash_fn, hashmap_equal_fn equal_fn, void *ctx) { struct hashmap *map = malloc(sizeof(struct hashmap)); if (!map) return ERR_PTR(-ENOMEM); hashmap__init(map, hash_fn, equal_fn, ctx); return map; } void hashmap__clear(struct hashmap *map) { struct hashmap_entry *cur, *tmp; size_t bkt; hashmap__for_each_entry_safe(map, cur, tmp, bkt) { free(cur); } free(map->buckets); map->buckets = NULL; map->cap = map->cap_bits = map->sz = 0; } void hashmap__free(struct hashmap *map) { if (IS_ERR_OR_NULL(map)) return; hashmap__clear(map); free(map); } size_t hashmap__size(const struct hashmap *map) { return map->sz; } size_t hashmap__capacity(const struct hashmap *map) { return map->cap; } static bool hashmap_needs_to_grow(struct hashmap *map) { /* grow if empty or more than 75% filled */ return (map->cap == 0) || ((map->sz + 1) * 4 / 3 > map->cap); } static int hashmap_grow(struct hashmap *map) { struct hashmap_entry **new_buckets; struct hashmap_entry *cur, *tmp; size_t new_cap_bits, new_cap; size_t h, bkt; new_cap_bits = map->cap_bits + 1; if (new_cap_bits < HASHMAP_MIN_CAP_BITS) new_cap_bits = HASHMAP_MIN_CAP_BITS; new_cap = 1UL << new_cap_bits; new_buckets = calloc(new_cap, sizeof(new_buckets[0])); if (!new_buckets) return -ENOMEM; hashmap__for_each_entry_safe(map, cur, tmp, bkt) { h = hash_bits(map->hash_fn(cur->key, map->ctx), new_cap_bits); hashmap_add_entry(&new_buckets[h], cur); } map->cap = new_cap; map->cap_bits = new_cap_bits; free(map->buckets); map->buckets = new_buckets; return 0; } static bool hashmap_find_entry(const struct hashmap *map, const long key, size_t hash, struct hashmap_entry ***pprev, struct hashmap_entry **entry) { struct hashmap_entry *cur, **prev_ptr; if (!map->buckets) return false; for (prev_ptr = &map->buckets[hash], cur = *prev_ptr; cur; prev_ptr = &cur->next, cur = cur->next) { if (map->equal_fn(cur->key, key, map->ctx)) { if (pprev) *pprev = prev_ptr; *entry = cur; return true; } } return false; } int hashmap_insert(struct hashmap *map, long key, long value, enum hashmap_insert_strategy strategy, long *old_key, long *old_value) { struct hashmap_entry *entry; size_t h; int err; if (old_key) *old_key = 0; if (old_value) *old_value = 0; h = hash_bits(map->hash_fn(key, map->ctx), map->cap_bits); if (strategy != HASHMAP_APPEND && hashmap_find_entry(map, key, h, NULL, &entry)) { if (old_key) *old_key = entry->key; if (old_value) *old_value = entry->value; if (strategy == HASHMAP_SET || strategy == HASHMAP_UPDATE) { entry->key = key; entry->value = value; return 0; } else if (strategy == HASHMAP_ADD) { return -EEXIST; } } if (strategy == HASHMAP_UPDATE) return -ENOENT; if (hashmap_needs_to_grow(map)) { err = hashmap_grow(map); if (err) return err; h = hash_bits(map->hash_fn(key, map->ctx), map->cap_bits); } entry = malloc(sizeof(struct hashmap_entry)); if (!entry) return -ENOMEM; entry->key = key; entry->value = value; hashmap_add_entry(&map->buckets[h], entry); map->sz++; return 0; } bool hashmap_find(const struct hashmap *map, long key, long *value) { struct hashmap_entry *entry; size_t h; h = hash_bits(map->hash_fn(key, map->ctx), map->cap_bits); if (!hashmap_find_entry(map, key, h, NULL, &entry)) return false; if (value) *value = entry->value; return true; } bool hashmap_delete(struct hashmap *map, long key, long *old_key, long *old_value) { struct hashmap_entry **pprev, *entry; size_t h; h = hash_bits(map->hash_fn(key, map->ctx), map->cap_bits); if (!hashmap_find_entry(map, key, h, &pprev, &entry)) return false; if (old_key) *old_key = entry->key; if (old_value) *old_value = entry->value; hashmap_del_entry(pprev, entry); free(entry); map->sz--; return true; }