1eee19501SIan Rogers // SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause)
2eee19501SIan Rogers
3eee19501SIan Rogers /*
4eee19501SIan Rogers * Generic non-thread safe hash map implementation.
5eee19501SIan Rogers *
6eee19501SIan Rogers * Copyright (c) 2019 Facebook
7eee19501SIan Rogers */
8eee19501SIan Rogers #include <stdint.h>
9eee19501SIan Rogers #include <stdlib.h>
10eee19501SIan Rogers #include <stdio.h>
11eee19501SIan Rogers #include <errno.h>
12eee19501SIan Rogers #include <linux/err.h>
13eee19501SIan Rogers #include "hashmap.h"
14eee19501SIan Rogers
15eee19501SIan Rogers /* make sure libbpf doesn't use kernel-only integer typedefs */
16eee19501SIan Rogers #pragma GCC poison u8 u16 u32 u64 s8 s16 s32 s64
17eee19501SIan Rogers
18e555b4b8SArnaldo Carvalho de Melo /* prevent accidental re-addition of reallocarray() */
19e555b4b8SArnaldo Carvalho de Melo #pragma GCC poison reallocarray
20e555b4b8SArnaldo Carvalho de Melo
21eee19501SIan Rogers /* start with 4 buckets */
22eee19501SIan Rogers #define HASHMAP_MIN_CAP_BITS 2
23eee19501SIan Rogers
hashmap_add_entry(struct hashmap_entry ** pprev,struct hashmap_entry * entry)24eee19501SIan Rogers static void hashmap_add_entry(struct hashmap_entry **pprev,
25eee19501SIan Rogers struct hashmap_entry *entry)
26eee19501SIan Rogers {
27eee19501SIan Rogers entry->next = *pprev;
28eee19501SIan Rogers *pprev = entry;
29eee19501SIan Rogers }
30eee19501SIan Rogers
hashmap_del_entry(struct hashmap_entry ** pprev,struct hashmap_entry * entry)31eee19501SIan Rogers static void hashmap_del_entry(struct hashmap_entry **pprev,
32eee19501SIan Rogers struct hashmap_entry *entry)
33eee19501SIan Rogers {
34eee19501SIan Rogers *pprev = entry->next;
35eee19501SIan Rogers entry->next = NULL;
36eee19501SIan Rogers }
37eee19501SIan Rogers
hashmap__init(struct hashmap * map,hashmap_hash_fn hash_fn,hashmap_equal_fn equal_fn,void * ctx)38eee19501SIan Rogers void hashmap__init(struct hashmap *map, hashmap_hash_fn hash_fn,
39eee19501SIan Rogers hashmap_equal_fn equal_fn, void *ctx)
40eee19501SIan Rogers {
41eee19501SIan Rogers map->hash_fn = hash_fn;
42eee19501SIan Rogers map->equal_fn = equal_fn;
43eee19501SIan Rogers map->ctx = ctx;
44eee19501SIan Rogers
45eee19501SIan Rogers map->buckets = NULL;
46eee19501SIan Rogers map->cap = 0;
47eee19501SIan Rogers map->cap_bits = 0;
48eee19501SIan Rogers map->sz = 0;
49eee19501SIan Rogers }
50eee19501SIan Rogers
hashmap__new(hashmap_hash_fn hash_fn,hashmap_equal_fn equal_fn,void * ctx)51eee19501SIan Rogers struct hashmap *hashmap__new(hashmap_hash_fn hash_fn,
52eee19501SIan Rogers hashmap_equal_fn equal_fn,
53eee19501SIan Rogers void *ctx)
54eee19501SIan Rogers {
55eee19501SIan Rogers struct hashmap *map = malloc(sizeof(struct hashmap));
56eee19501SIan Rogers
57eee19501SIan Rogers if (!map)
58eee19501SIan Rogers return ERR_PTR(-ENOMEM);
59eee19501SIan Rogers hashmap__init(map, hash_fn, equal_fn, ctx);
60eee19501SIan Rogers return map;
61eee19501SIan Rogers }
62eee19501SIan Rogers
hashmap__clear(struct hashmap * map)63eee19501SIan Rogers void hashmap__clear(struct hashmap *map)
64eee19501SIan Rogers {
65eee19501SIan Rogers struct hashmap_entry *cur, *tmp;
66eee19501SIan Rogers size_t bkt;
67eee19501SIan Rogers
68eee19501SIan Rogers hashmap__for_each_entry_safe(map, cur, tmp, bkt) {
69eee19501SIan Rogers free(cur);
70eee19501SIan Rogers }
71eee19501SIan Rogers free(map->buckets);
72eee19501SIan Rogers map->buckets = NULL;
73eee19501SIan Rogers map->cap = map->cap_bits = map->sz = 0;
74eee19501SIan Rogers }
75eee19501SIan Rogers
hashmap__free(struct hashmap * map)76eee19501SIan Rogers void hashmap__free(struct hashmap *map)
77eee19501SIan Rogers {
784d4d00ddSArnaldo Carvalho de Melo if (IS_ERR_OR_NULL(map))
79eee19501SIan Rogers return;
80eee19501SIan Rogers
81eee19501SIan Rogers hashmap__clear(map);
82eee19501SIan Rogers free(map);
83eee19501SIan Rogers }
84eee19501SIan Rogers
hashmap__size(const struct hashmap * map)85eee19501SIan Rogers size_t hashmap__size(const struct hashmap *map)
86eee19501SIan Rogers {
87eee19501SIan Rogers return map->sz;
88eee19501SIan Rogers }
89eee19501SIan Rogers
hashmap__capacity(const struct hashmap * map)90eee19501SIan Rogers size_t hashmap__capacity(const struct hashmap *map)
91eee19501SIan Rogers {
92eee19501SIan Rogers return map->cap;
93eee19501SIan Rogers }
94eee19501SIan Rogers
hashmap_needs_to_grow(struct hashmap * map)95eee19501SIan Rogers static bool hashmap_needs_to_grow(struct hashmap *map)
96eee19501SIan Rogers {
97eee19501SIan Rogers /* grow if empty or more than 75% filled */
98eee19501SIan Rogers return (map->cap == 0) || ((map->sz + 1) * 4 / 3 > map->cap);
99eee19501SIan Rogers }
100eee19501SIan Rogers
hashmap_grow(struct hashmap * map)101eee19501SIan Rogers static int hashmap_grow(struct hashmap *map)
102eee19501SIan Rogers {
103eee19501SIan Rogers struct hashmap_entry **new_buckets;
104eee19501SIan Rogers struct hashmap_entry *cur, *tmp;
105eee19501SIan Rogers size_t new_cap_bits, new_cap;
106eee19501SIan Rogers size_t h, bkt;
107eee19501SIan Rogers
108eee19501SIan Rogers new_cap_bits = map->cap_bits + 1;
109eee19501SIan Rogers if (new_cap_bits < HASHMAP_MIN_CAP_BITS)
110eee19501SIan Rogers new_cap_bits = HASHMAP_MIN_CAP_BITS;
111eee19501SIan Rogers
112eee19501SIan Rogers new_cap = 1UL << new_cap_bits;
113eee19501SIan Rogers new_buckets = calloc(new_cap, sizeof(new_buckets[0]));
114eee19501SIan Rogers if (!new_buckets)
115eee19501SIan Rogers return -ENOMEM;
116eee19501SIan Rogers
117eee19501SIan Rogers hashmap__for_each_entry_safe(map, cur, tmp, bkt) {
118eee19501SIan Rogers h = hash_bits(map->hash_fn(cur->key, map->ctx), new_cap_bits);
119eee19501SIan Rogers hashmap_add_entry(&new_buckets[h], cur);
120eee19501SIan Rogers }
121eee19501SIan Rogers
122eee19501SIan Rogers map->cap = new_cap;
123eee19501SIan Rogers map->cap_bits = new_cap_bits;
124eee19501SIan Rogers free(map->buckets);
125eee19501SIan Rogers map->buckets = new_buckets;
126eee19501SIan Rogers
127eee19501SIan Rogers return 0;
128eee19501SIan Rogers }
129eee19501SIan Rogers
hashmap_find_entry(const struct hashmap * map,const long key,size_t hash,struct hashmap_entry *** pprev,struct hashmap_entry ** entry)130eee19501SIan Rogers static bool hashmap_find_entry(const struct hashmap *map,
131*c302378bSEduard Zingerman const long key, size_t hash,
132eee19501SIan Rogers struct hashmap_entry ***pprev,
133eee19501SIan Rogers struct hashmap_entry **entry)
134eee19501SIan Rogers {
135eee19501SIan Rogers struct hashmap_entry *cur, **prev_ptr;
136eee19501SIan Rogers
137eee19501SIan Rogers if (!map->buckets)
138eee19501SIan Rogers return false;
139eee19501SIan Rogers
140eee19501SIan Rogers for (prev_ptr = &map->buckets[hash], cur = *prev_ptr;
141eee19501SIan Rogers cur;
142eee19501SIan Rogers prev_ptr = &cur->next, cur = cur->next) {
143eee19501SIan Rogers if (map->equal_fn(cur->key, key, map->ctx)) {
144eee19501SIan Rogers if (pprev)
145eee19501SIan Rogers *pprev = prev_ptr;
146eee19501SIan Rogers *entry = cur;
147eee19501SIan Rogers return true;
148eee19501SIan Rogers }
149eee19501SIan Rogers }
150eee19501SIan Rogers
151eee19501SIan Rogers return false;
152eee19501SIan Rogers }
153eee19501SIan Rogers
hashmap_insert(struct hashmap * map,long key,long value,enum hashmap_insert_strategy strategy,long * old_key,long * old_value)154*c302378bSEduard Zingerman int hashmap_insert(struct hashmap *map, long key, long value,
155eee19501SIan Rogers enum hashmap_insert_strategy strategy,
156*c302378bSEduard Zingerman long *old_key, long *old_value)
157eee19501SIan Rogers {
158eee19501SIan Rogers struct hashmap_entry *entry;
159eee19501SIan Rogers size_t h;
160eee19501SIan Rogers int err;
161eee19501SIan Rogers
162eee19501SIan Rogers if (old_key)
163*c302378bSEduard Zingerman *old_key = 0;
164eee19501SIan Rogers if (old_value)
165*c302378bSEduard Zingerman *old_value = 0;
166eee19501SIan Rogers
167eee19501SIan Rogers h = hash_bits(map->hash_fn(key, map->ctx), map->cap_bits);
168eee19501SIan Rogers if (strategy != HASHMAP_APPEND &&
169eee19501SIan Rogers hashmap_find_entry(map, key, h, NULL, &entry)) {
170eee19501SIan Rogers if (old_key)
171eee19501SIan Rogers *old_key = entry->key;
172eee19501SIan Rogers if (old_value)
173eee19501SIan Rogers *old_value = entry->value;
174eee19501SIan Rogers
175eee19501SIan Rogers if (strategy == HASHMAP_SET || strategy == HASHMAP_UPDATE) {
176eee19501SIan Rogers entry->key = key;
177eee19501SIan Rogers entry->value = value;
178eee19501SIan Rogers return 0;
179eee19501SIan Rogers } else if (strategy == HASHMAP_ADD) {
180eee19501SIan Rogers return -EEXIST;
181eee19501SIan Rogers }
182eee19501SIan Rogers }
183eee19501SIan Rogers
184eee19501SIan Rogers if (strategy == HASHMAP_UPDATE)
185eee19501SIan Rogers return -ENOENT;
186eee19501SIan Rogers
187eee19501SIan Rogers if (hashmap_needs_to_grow(map)) {
188eee19501SIan Rogers err = hashmap_grow(map);
189eee19501SIan Rogers if (err)
190eee19501SIan Rogers return err;
191eee19501SIan Rogers h = hash_bits(map->hash_fn(key, map->ctx), map->cap_bits);
192eee19501SIan Rogers }
193eee19501SIan Rogers
194eee19501SIan Rogers entry = malloc(sizeof(struct hashmap_entry));
195eee19501SIan Rogers if (!entry)
196eee19501SIan Rogers return -ENOMEM;
197eee19501SIan Rogers
198eee19501SIan Rogers entry->key = key;
199eee19501SIan Rogers entry->value = value;
200eee19501SIan Rogers hashmap_add_entry(&map->buckets[h], entry);
201eee19501SIan Rogers map->sz++;
202eee19501SIan Rogers
203eee19501SIan Rogers return 0;
204eee19501SIan Rogers }
205eee19501SIan Rogers
hashmap_find(const struct hashmap * map,long key,long * value)206*c302378bSEduard Zingerman bool hashmap_find(const struct hashmap *map, long key, long *value)
207eee19501SIan Rogers {
208eee19501SIan Rogers struct hashmap_entry *entry;
209eee19501SIan Rogers size_t h;
210eee19501SIan Rogers
211eee19501SIan Rogers h = hash_bits(map->hash_fn(key, map->ctx), map->cap_bits);
212eee19501SIan Rogers if (!hashmap_find_entry(map, key, h, NULL, &entry))
213eee19501SIan Rogers return false;
214eee19501SIan Rogers
215eee19501SIan Rogers if (value)
216eee19501SIan Rogers *value = entry->value;
217eee19501SIan Rogers return true;
218eee19501SIan Rogers }
219eee19501SIan Rogers
hashmap_delete(struct hashmap * map,long key,long * old_key,long * old_value)220*c302378bSEduard Zingerman bool hashmap_delete(struct hashmap *map, long key,
221*c302378bSEduard Zingerman long *old_key, long *old_value)
222eee19501SIan Rogers {
223eee19501SIan Rogers struct hashmap_entry **pprev, *entry;
224eee19501SIan Rogers size_t h;
225eee19501SIan Rogers
226eee19501SIan Rogers h = hash_bits(map->hash_fn(key, map->ctx), map->cap_bits);
227eee19501SIan Rogers if (!hashmap_find_entry(map, key, h, &pprev, &entry))
228eee19501SIan Rogers return false;
229eee19501SIan Rogers
230eee19501SIan Rogers if (old_key)
231eee19501SIan Rogers *old_key = entry->key;
232eee19501SIan Rogers if (old_value)
233eee19501SIan Rogers *old_value = entry->value;
234eee19501SIan Rogers
235eee19501SIan Rogers hashmap_del_entry(pprev, entry);
236eee19501SIan Rogers free(entry);
237eee19501SIan Rogers map->sz--;
238eee19501SIan Rogers
239eee19501SIan Rogers return true;
240eee19501SIan Rogers }
241