-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
libbpf: add resizable non-thread safe internal hashmap
There is a need for fast point lookups inside libbpf for multiple use cases (e.g., name resolution for BTF-to-C conversion, by-name lookups in BTF for upcoming BPF CO-RE relocation support, etc). This patch implements simple resizable non-thread safe hashmap using single linked list chains. Four different insert strategies are supported: - HASHMAP_ADD - only add key/value if key doesn't exist yet; - HASHMAP_SET - add key/value pair if key doesn't exist yet; otherwise, update value; - HASHMAP_UPDATE - update value, if key already exists; otherwise, do nothing and return -ENOENT; - HASHMAP_APPEND - always add key/value pair, even if key already exists. This turns hashmap into a multimap by allowing multiple values to be associated with the same key. Most useful read API for such hashmap is hashmap__for_each_key_entry() iteration. If hashmap__find() is still used, it will return last inserted key/value entry (first in a bucket chain). For HASHMAP_SET and HASHMAP_UPDATE, old key/value pair is returned, so that calling code can handle proper memory management, if necessary. Signed-off-by: Andrii Nakryiko <andriin@fb.com> Signed-off-by: Alexei Starovoitov <ast@kernel.org>
- Loading branch information
Andrii Nakryiko
authored and
Alexei Starovoitov
committed
May 24, 2019
1 parent
9db3243
commit e3b9242
Showing
3 changed files
with
403 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1 @@ | ||
libbpf-y := libbpf.o bpf.o nlattr.o btf.o libbpf_errno.o str_error.o netlink.o bpf_prog_linfo.o libbpf_probes.o xsk.o | ||
libbpf-y := libbpf.o bpf.o nlattr.o btf.o libbpf_errno.o str_error.o netlink.o bpf_prog_linfo.o libbpf_probes.o xsk.o hashmap.o |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,229 @@ | ||
// 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" | ||
|
||
/* 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) | ||
{ | ||
free(map->buckets); | ||
map->cap = map->cap_bits = map->sz = 0; | ||
} | ||
|
||
void hashmap__free(struct hashmap *map) | ||
{ | ||
if (!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; | ||
int 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 void *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, const void *key, void *value, | ||
enum hashmap_insert_strategy strategy, | ||
const void **old_key, void **old_value) | ||
{ | ||
struct hashmap_entry *entry; | ||
size_t h; | ||
int err; | ||
|
||
if (old_key) | ||
*old_key = NULL; | ||
if (old_value) | ||
*old_value = NULL; | ||
|
||
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, const void *key, void **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, const void *key, | ||
const void **old_key, void **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; | ||
} | ||
|
Oops, something went wrong.