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