240 lines · 4.9 KB · C
RawDownload
// 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;}