root/tools/perf/util/hashmap.c
// 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;
}