From: Ian Rogers <irogers@google.com>
To: Peter Zijlstra <peterz@infradead.org>,
Ingo Molnar <mingo@redhat.com>,
Arnaldo Carvalho de Melo <acme@kernel.org>,
Namhyung Kim <namhyung@kernel.org>
Cc: Jiri Olsa <jolsa@kernel.org>, Ian Rogers <irogers@google.com>,
Adrian Hunter <adrian.hunter@intel.com>,
James Clark <james.clark@linaro.org>,
linux-perf-users@vger.kernel.org, linux-kernel@vger.kernel.org
Subject: [PATCH v1 08/26] perf hashmap: Introduce generic types and macros
Date: Fri, 13 Nov 2026 13:48:32 -0800 [thread overview]
Message-ID: <20261113214850.3329699-9-irogers@google.com> (raw)
In-Reply-To: <20261113214850.3329699-1-irogers@google.com>
Add DECLARE_HASHMAP(name, key_type, value_type) and type-checked
hashmap__* wrapper macros around struct __hashmap while keeping legacy
struct hashmap compatible so callers can be migrated one hashmap at a
time:
- Encode key_type and value_type via phantom pointer members in a union
with struct __hashmap so wrapper structs preserve identical size and
alignment across 32-bit and 64-bit targets, with _Static_assert checks
that key_type and value_type fit in a long.
- Generate typed <name>__new() and NULL-safe <name>__free() helpers,
plus pointer-taking hashmap__init(), hashmap__clear(), hashmap__free(),
hashmap__size(), hashmap__capacity(), hashmap__insert(),
hashmap__add(), hashmap__set(), hashmap__update(), hashmap__append(),
hashmap__find(), and hashmap__delete() macros.
- Marshal output parameters (value_ptr, old_key, old_value) through
long-sized temporaries and assign via the declared types so sub-long
types (such as bool, int, or pid_t) are safe from stack buffer
overflows, endianness bugs, and strict-aliasing violations, while
allowing NULL output pointers and const-qualified lookup keys.
- Implement hashmap__for_each{,_safe}() and
hashmap__for_each_key{,_safe}() using single-loop __hashmap_iter and
__hashmap_key_iter helpers so 'break' exits the entire iteration,
bucket cursors are internal, and element deletion during iteration is
safe.
- Add a 'Generic hashmap' perf test suite exercising integer, string,
and sub-long bool/int32 maps, NULL frees, const lookups, early loop
breaks, and in-loop deletions.
Signed-off-by: Ian Rogers <irogers@google.com>
---
tools/perf/tests/Build | 1 +
tools/perf/tests/builtin-test.c | 1 +
tools/perf/tests/hashmap.c | 192 +++++++++
tools/perf/tests/tests.h | 1 +
tools/perf/util/hashmap.c | 30 +-
tools/perf/util/hashmap.h | 675 ++++++++++++++++++++++++++------
6 files changed, 773 insertions(+), 127 deletions(-)
create mode 100644 tools/perf/tests/hashmap.c
diff --git a/tools/perf/tests/Build b/tools/perf/tests/Build
index 5eebb2ec5dde..2c2560613323 100644
--- a/tools/perf/tests/Build
+++ b/tools/perf/tests/Build
@@ -74,6 +74,7 @@ perf-test-y += hwmon_pmu.o
perf-test-y += tool_pmu.o
perf-test-y += subcmd-help.o
perf-test-y += kallsyms-split.o
+perf-test-y += hashmap.o
ifeq ($(SRCARCH),$(filter $(SRCARCH),x86 arm arm64 powerpc riscv))
perf-test-$(CONFIG_DWARF_UNWIND) += dwarf-unwind.o
diff --git a/tools/perf/tests/builtin-test.c b/tools/perf/tests/builtin-test.c
index 327483f96401..24e7f8443e94 100644
--- a/tools/perf/tests/builtin-test.c
+++ b/tools/perf/tests/builtin-test.c
@@ -192,6 +192,7 @@ static struct test_suite *generic_tests[] = {
&suite__util,
&suite__subcmd_help,
&suite__kallsyms_split,
+ &suite__hashmap,
NULL,
};
diff --git a/tools/perf/tests/hashmap.c b/tools/perf/tests/hashmap.c
new file mode 100644
index 000000000000..5a4f39eb8c17
--- /dev/null
+++ b/tools/perf/tests/hashmap.c
@@ -0,0 +1,192 @@
+// SPDX-License-Identifier: GPL-2.0
+#include "util/hashmap.h"
+#include "tests.h"
+#include <errno.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <string.h>
+
+DECLARE_HASHMAP(int_map, long, long);
+DECLARE_HASHMAP(bool_map, char *, bool);
+DECLARE_HASHMAP(int32_map, int, const char *);
+
+static size_t int_hash(long key, void *ctx __maybe_unused)
+{
+ return (size_t)key;
+}
+
+static bool int_equal(long key1, long key2, void *ctx __maybe_unused)
+{
+ return key1 == key2;
+}
+
+static size_t str_hash_fn(long key, void *ctx __maybe_unused)
+{
+ return str_hash((const char *)key);
+}
+
+static bool str_equal_fn(long key1, long key2, void *ctx __maybe_unused)
+{
+ return strcmp((const char *)key1, (const char *)key2) == 0;
+}
+
+static int test_int_map(void)
+{
+ struct int_map imap;
+ long val = -1, old_k = -1, old_v = -1;
+ long k, v, sum = 0;
+
+ hashmap__init(&imap, int_hash, int_equal, NULL);
+ TEST_ASSERT_EQUAL("initial size", (int)hashmap__size(&imap), 0);
+
+ TEST_ASSERT_EQUAL("add 1", hashmap__add(&imap, 1L, 10L), 0);
+ TEST_ASSERT_EQUAL("add 2", hashmap__add(&imap, 2L, 20L), 0);
+ TEST_ASSERT_EQUAL("add dup", hashmap__add(&imap, 1L, 99L), -EEXIST);
+ TEST_ASSERT_EQUAL("size after add", (int)hashmap__size(&imap), 2);
+ TEST_ASSERT_VAL("capacity > 0", hashmap__capacity(&imap) >= 2);
+
+ TEST_ASSERT_VAL("find 1", hashmap__find(&imap, 1L, &val));
+ TEST_ASSERT_EQUAL("find 1 val", (int)val, 10);
+ TEST_ASSERT_VAL("find 2 NULL out", hashmap__find(&imap, 2L, NULL));
+ TEST_ASSERT_VAL("find 3 missing", !hashmap__find(&imap, 3L, &val));
+
+ /* Update missing vs existing */
+ TEST_ASSERT_EQUAL("update missing",
+ hashmap__update(&imap, 3L, 30L, &old_k, &old_v), -ENOENT);
+ TEST_ASSERT_EQUAL("update existing",
+ hashmap__update(&imap, 1L, 11L, &old_k, &old_v), 0);
+ TEST_ASSERT_EQUAL("update old_k", (int)old_k, 1);
+ TEST_ASSERT_EQUAL("update old_v", (int)old_v, 10);
+
+ /* Set new vs existing */
+ TEST_ASSERT_EQUAL("set new",
+ hashmap__set(&imap, 3L, 30L, &old_k, &old_v), 0);
+ TEST_ASSERT_EQUAL("set new old_k", (int)old_k, 0);
+ TEST_ASSERT_EQUAL("set new old_v", (int)old_v, 0);
+
+ /* Multimap append + for_each_key */
+ TEST_ASSERT_EQUAL("append 2", hashmap__append(&imap, 2L, 200L), 0);
+ sum = 0;
+ hashmap__for_each_key(&imap, 2L, v)
+ sum += v;
+ TEST_ASSERT_EQUAL("for_each_key sum", (int)sum, 220);
+
+ /* Delete one of the key=2 entries; verify missing delete zeroes outputs */
+ TEST_ASSERT_VAL("delete 2", hashmap__delete(&imap, 2L, &old_k, &old_v));
+ TEST_ASSERT_EQUAL("delete old_k", (int)old_k, 2);
+ TEST_ASSERT_VAL("delete missing", !hashmap__delete(&imap, 999L, &old_k, &old_v));
+ TEST_ASSERT_EQUAL("delete missing old_k zeroed", (int)old_k, 0);
+ TEST_ASSERT_EQUAL("delete missing old_v zeroed", (int)old_v, 0);
+
+ sum = 0;
+ hashmap__for_each(&imap, k, v)
+ sum += v;
+ TEST_ASSERT_EQUAL("for_each sum", (int)sum, 11 + 20 + 30);
+
+ hashmap__clear(&imap);
+ TEST_ASSERT_EQUAL("cleared size", (int)hashmap__size(&imap), 0);
+ return 0;
+}
+
+static int test_bool_and_str_map(void)
+{
+ struct bool_map *bmap;
+ char *k1, *k2, *k2_dup, *old_key = NULL;
+ char *iter_key;
+ const char *const_lookup = "alpha";
+ /* Guard bytes around bool to catch any >1-byte write overflow. */
+ struct {
+ unsigned char pre[8];
+ bool val;
+ unsigned char post[8];
+ } guard;
+ bool old_val = true;
+
+ /* Freeing NULL must be a safe no-op (including under UBSAN). */
+ bool_map__free(NULL);
+
+ bmap = bool_map__new(str_hash_fn, str_equal_fn, NULL);
+ TEST_ASSERT_VAL("bool_map__new", bmap != NULL);
+
+ k1 = strdup("alpha");
+ k2 = strdup("beta");
+ k2_dup = strdup("beta");
+ TEST_ASSERT_VAL("strdup", k1 && k2 && k2_dup);
+
+ TEST_ASSERT_EQUAL("add alpha", hashmap__add(bmap, k1, true), 0);
+ TEST_ASSERT_EQUAL("add beta", hashmap__add(bmap, k2, false), 0);
+
+ memset(&guard, 0x5a, sizeof(guard));
+ TEST_ASSERT_VAL("find const key", hashmap__find(bmap, const_lookup, &guard.val));
+ TEST_ASSERT_VAL("alpha is true", guard.val == true);
+ for (size_t i = 0; i < sizeof(guard.pre); i++) {
+ TEST_ASSERT_EQUAL("pre guard intact", guard.pre[i], 0x5a);
+ TEST_ASSERT_EQUAL("post guard intact", guard.post[i], 0x5a);
+ }
+
+ TEST_ASSERT_VAL("find beta", hashmap__find(bmap, "beta", &guard.val));
+ TEST_ASSERT_VAL("beta is false", guard.val == false);
+
+ TEST_ASSERT_EQUAL("set beta",
+ hashmap__set(bmap, k2_dup, true, &old_key, &old_val), 0);
+ TEST_ASSERT_VAL("old_key == k2", old_key == k2);
+ TEST_ASSERT_VAL("old_val == false", old_val == false);
+ free(old_key);
+
+ hashmap__for_each(bmap, iter_key, old_val)
+ free(iter_key);
+ bool_map__free(bmap);
+ return 0;
+}
+
+static int test_iter_break_and_safe_delete(void)
+{
+ struct int32_map map;
+ const struct int32_map *cmap = ↦
+ int k, count = 0;
+ const char *v;
+
+ hashmap__init(&map, int_hash, int_equal, NULL);
+
+ /* Insert enough entries (including negative keys) to span multiple buckets. */
+ for (int i = -10; i < 20; i++)
+ TEST_ASSERT_EQUAL("add int32", hashmap__add(&map, i, "entry"), 0);
+
+ TEST_ASSERT_VAL("find negative key on const map",
+ hashmap__find(cmap, -7, &v) && strcmp(v, "entry") == 0);
+
+ /* Verify 'break' exits the entire hashmap__for_each loop, not just one bucket. */
+ hashmap__for_each(cmap, k, v) {
+ count++;
+ if (count == 3)
+ break;
+ }
+ TEST_ASSERT_EQUAL("break stops outer iteration", count, 3);
+
+ /* Verify safe deletion during iteration. */
+ hashmap__for_each_safe(&map, k, v) {
+ if (k < 0)
+ TEST_ASSERT_VAL("delete negative", hashmap__delete(&map, k, NULL, NULL));
+ }
+ TEST_ASSERT_EQUAL("remaining non-negative", (int)hashmap__size(&map), 20);
+
+ hashmap__clear(&map);
+ return 0;
+}
+
+static int test__hashmap(struct test_suite *t __maybe_unused, int subtest __maybe_unused)
+{
+ int err;
+
+ err = test_int_map();
+ if (err)
+ return err;
+
+ err = test_bool_and_str_map();
+ if (err)
+ return err;
+
+ return test_iter_break_and_safe_delete();
+}
+
+DEFINE_SUITE("Generic hashmap", hashmap);
diff --git a/tools/perf/tests/tests.h b/tools/perf/tests/tests.h
index af822885890d..e9cbc0dcd778 100644
--- a/tools/perf/tests/tests.h
+++ b/tools/perf/tests/tests.h
@@ -203,6 +203,7 @@ DECLARE_SUITE(util);
DECLARE_SUITE(uncore_event_sorting);
DECLARE_SUITE(subcmd_help);
DECLARE_SUITE(kallsyms_split);
+DECLARE_SUITE(hashmap);
/*
* PowerPC and S390 do not support creation of instruction breakpoints using the
diff --git a/tools/perf/util/hashmap.c b/tools/perf/util/hashmap.c
index f2d8beeac27a..f6bfaeaee9d7 100644
--- a/tools/perf/util/hashmap.c
+++ b/tools/perf/util/hashmap.c
@@ -35,7 +35,7 @@ static void __hashmap__del_entry(struct hashmap_entry **pprev,
entry->next = NULL;
}
-void __hashmap__init(struct hashmap *map, hashmap_hash_fn hash_fn,
+void __hashmap__init(struct __hashmap *map, hashmap_hash_fn hash_fn,
hashmap_equal_fn equal_fn, void *ctx)
{
map->hash_fn = hash_fn;
@@ -48,11 +48,11 @@ void __hashmap__init(struct hashmap *map, hashmap_hash_fn hash_fn,
map->sz = 0;
}
-struct hashmap *__hashmap__new(hashmap_hash_fn hash_fn,
- hashmap_equal_fn equal_fn,
- void *ctx)
+struct __hashmap *__hashmap__new(hashmap_hash_fn hash_fn,
+ hashmap_equal_fn equal_fn,
+ void *ctx)
{
- struct hashmap *map = malloc(sizeof(struct hashmap));
+ struct __hashmap *map = malloc(sizeof(*map));
if (!map) {
errno = ENOMEM;
@@ -62,7 +62,7 @@ struct hashmap *__hashmap__new(hashmap_hash_fn hash_fn,
return map;
}
-void __hashmap__clear(struct hashmap *map)
+void __hashmap__clear(struct __hashmap *map)
{
struct hashmap_entry *cur, *tmp;
size_t bkt;
@@ -75,7 +75,7 @@ void __hashmap__clear(struct hashmap *map)
map->cap = map->cap_bits = map->sz = 0;
}
-void __hashmap__free(struct hashmap *map)
+void __hashmap__free(struct __hashmap *map)
{
if (!map)
return;
@@ -84,23 +84,23 @@ void __hashmap__free(struct hashmap *map)
free(map);
}
-size_t __hashmap__size(const struct hashmap *map)
+size_t __hashmap__size(const struct __hashmap *map)
{
return map->sz;
}
-size_t __hashmap__capacity(const struct hashmap *map)
+size_t __hashmap__capacity(const struct __hashmap *map)
{
return map->cap;
}
-static bool __hashmap__needs_to_grow(struct hashmap *map)
+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)
+static int __hashmap__grow(struct __hashmap *map)
{
struct hashmap_entry **new_buckets;
struct hashmap_entry *cur, *tmp;
@@ -129,7 +129,7 @@ static int __hashmap__grow(struct hashmap *map)
return 0;
}
-static bool __hashmap__find_entry(const struct hashmap *map,
+static bool __hashmap__find_entry(const struct __hashmap *map,
const long key, size_t hash,
struct hashmap_entry ***pprev,
struct hashmap_entry **entry)
@@ -153,7 +153,7 @@ static bool __hashmap__find_entry(const struct hashmap *map,
return false;
}
-int __hashmap__insert(struct hashmap *map, long key, long value,
+int __hashmap__insert(struct __hashmap *map, long key, long value,
enum hashmap_insert_strategy strategy,
void *old_key, void *old_value)
{
@@ -205,7 +205,7 @@ int __hashmap__insert(struct hashmap *map, long key, long value,
return 0;
}
-bool __hashmap__find(const struct hashmap *map, long key, void *value)
+bool __hashmap__find(const struct __hashmap *map, long key, void *value)
{
struct hashmap_entry *entry;
size_t h;
@@ -219,7 +219,7 @@ bool __hashmap__find(const struct hashmap *map, long key, void *value)
return true;
}
-bool __hashmap__delete(struct hashmap *map, long key,
+bool __hashmap__delete(struct __hashmap *map, long key,
void *old_key, void *old_value)
{
struct hashmap_entry **pprev, *entry;
diff --git a/tools/perf/util/hashmap.h b/tools/perf/util/hashmap.h
index 4dcc2cf3ef1a..bb85a65486be 100644
--- a/tools/perf/util/hashmap.h
+++ b/tools/perf/util/hashmap.h
@@ -8,10 +8,20 @@
#ifndef __PERF_UTIL_HASHMAP_H
#define __PERF_UTIL_HASHMAP_H
+#include <errno.h>
#include <stdbool.h>
#include <stddef.h>
+#include <stdlib.h>
#include <limits.h>
+#include <linux/compiler.h>
+/**
+ * hash_bits() - Shuffle hash value and return requested number of upper bits.
+ * @h: Raw hash value.
+ * @bits: Number of bits to return (0 returns 0).
+ *
+ * Return: Multiplicative hash truncated to @bits bits.
+ */
static inline size_t hash_bits(size_t h, int bits)
{
/* shuffle bits and return requested number of upper bits */
@@ -28,7 +38,12 @@ static inline size_t hash_bits(size_t h, int bits)
#endif
}
-/* generic C-string hashing function */
+/**
+ * str_hash() - Generic C-string hashing function.
+ * @s: NUL-terminated string to hash.
+ *
+ * Return: Unshuffled hash value suitable for hash_bits().
+ */
static inline size_t str_hash(const char *s)
{
size_t h = 0;
@@ -40,22 +55,42 @@ static inline size_t str_hash(const char *s)
return h;
}
+/**
+ * typedef hashmap_hash_fn - Hash callback computing a key's hash value.
+ * @key: Key value cast to long.
+ * @ctx: Optional user context pointer passed at hashmap initialization.
+ *
+ * Return: Hash value for @key (shuffled internally via hash_bits()).
+ */
typedef size_t (*hashmap_hash_fn)(long key, void *ctx);
+
+/**
+ * typedef hashmap_equal_fn - Equality callback comparing two keys.
+ * @key1: First key value cast to long.
+ * @key2: Second key value cast to long.
+ * @ctx: Optional user context pointer passed at hashmap initialization.
+ *
+ * Return: true if @key1 and @key2 are equal, false otherwise.
+ */
typedef bool (*hashmap_equal_fn)(long key1, long key2, void *ctx);
/*
- * Hashmap interface is polymorphic, keys and values could be either
- * long-sized integers or pointers, this is achieved as follows:
- * - interface functions that operate on keys and values are hidden
- * behind auxiliary macros, e.g. hashmap_insert <-> hashmap__insert;
- * - these auxiliary macros cast the key and value parameters as
- * long or long *, so the user does not have to specify the casts explicitly;
- * - for pointer parameters (e.g. old_key) the size of the pointed
- * type is verified by hashmap_cast_ptr using _Static_assert;
- * - when iterating using hashmap__for_each_* forms
- * hasmap_entry->key should be used for integer keys and
- * hasmap_entry->pkey should be used for pointer keys,
- * same goes for values.
+ * Typed hashmap interface:
+ *
+ * Hashmaps are declared with DECLARE_HASHMAP(name, key_type, value_type),
+ * where key_type and value_type may be any integer, enum, bool, or pointer
+ * type with sizeof <= sizeof(long):
+ * - DECLARE_HASHMAP() defines 'struct <name>' along with '<name>__new()' and
+ * '<name>__free()' allocation helpers;
+ * - phantom pointer members (__key, __value) in a union with the backing
+ * struct __hashmap encode the declared key and value types at zero
+ * runtime space cost and without altering struct size or alignment;
+ * - hashmap__* macros type-check keys, values, output pointers, and iteration
+ * cursor variables against the declared types at compile time;
+ * - output parameters (value_ptr, old_key, old_value) are marshaled through
+ * long temporaries and assigned via the declared types so sub-long types
+ * (e.g. bool, int, pid_t) are safe from buffer overflows, endianness bugs,
+ * and strict-aliasing violations.
*/
struct hashmap_entry {
union {
@@ -69,7 +104,7 @@ struct hashmap_entry {
struct hashmap_entry *next;
};
-struct hashmap {
+struct __hashmap {
hashmap_hash_fn hash_fn;
hashmap_equal_fn equal_fn;
void *ctx;
@@ -80,148 +115,564 @@ struct hashmap {
size_t sz;
};
-void __hashmap__init(struct hashmap *map, hashmap_hash_fn hash_fn,
- hashmap_equal_fn equal_fn, void *ctx);
-struct hashmap *__hashmap__new(hashmap_hash_fn hash_fn,
- hashmap_equal_fn equal_fn,
- void *ctx);
-void __hashmap__clear(struct hashmap *map);
-void __hashmap__free(struct hashmap *map);
+/* Legacy untyped hashmap, kept while migrating callers to DECLARE_HASHMAP. */
+struct hashmap {
+ union {
+ struct __hashmap map;
+ struct {
+ hashmap_hash_fn hash_fn;
+ hashmap_equal_fn equal_fn;
+ void *ctx;
+
+ struct hashmap_entry **buckets;
+ size_t cap;
+ size_t cap_bits;
+ size_t sz;
+ };
+ long *__key;
+ long *__value;
+ char __untyped;
+ };
+};
-size_t __hashmap__size(const struct hashmap *map);
-size_t __hashmap__capacity(const struct hashmap *map);
+void __hashmap__init(struct __hashmap *map, hashmap_hash_fn hash_fn,
+ hashmap_equal_fn equal_fn, void *ctx);
+struct __hashmap *__hashmap__new(hashmap_hash_fn hash_fn,
+ hashmap_equal_fn equal_fn,
+ void *ctx);
+void __hashmap__clear(struct __hashmap *map);
+void __hashmap__free(struct __hashmap *map);
-static inline void hashmap__init(struct hashmap *map, hashmap_hash_fn hash_fn,
- hashmap_equal_fn equal_fn, void *ctx)
-{
- __hashmap__init(map, hash_fn, equal_fn, ctx);
-}
+size_t __hashmap__size(const struct __hashmap *map);
+size_t __hashmap__capacity(const struct __hashmap *map);
static inline struct hashmap *hashmap__new(hashmap_hash_fn hash_fn,
hashmap_equal_fn equal_fn,
void *ctx)
{
- return __hashmap__new(hash_fn, equal_fn, ctx);
+ struct hashmap *map = malloc(sizeof(*map));
+
+ if (!map) {
+ errno = ENOMEM;
+ return NULL;
+ }
+ __hashmap__init(&map->map, hash_fn, equal_fn, ctx);
+ return map;
}
-static inline void hashmap__clear(struct hashmap *map)
+/**
+ * enum hashmap_insert_strategy - Strategy for hashmap__insert().
+ * @HASHMAP_ADD: Only add key/value if key doesn't exist yet; returns -EEXIST
+ * and optionally fills old_key/old_value if already present.
+ * @HASHMAP_SET: Add key/value pair if key doesn't exist yet; otherwise update
+ * key/value and optionally return previous key/value.
+ * @HASHMAP_UPDATE: Update key/value if key already exists; otherwise do
+ * nothing and return -ENOENT.
+ * @HASHMAP_APPEND: Always add key/value pair, even if key already exists.
+ * Turns the hashmap into a multimap allowing multiple values
+ * associated with the same key. Read via
+ * hashmap__for_each_key(); hashmap__find() returns the most
+ * recently inserted entry for the key.
+ */
+enum hashmap_insert_strategy {
+ HASHMAP_ADD,
+ HASHMAP_SET,
+ HASHMAP_UPDATE,
+ HASHMAP_APPEND,
+};
+
+int __hashmap__insert(struct __hashmap *map, long key, long value,
+ enum hashmap_insert_strategy strategy,
+ void *old_key, void *old_value);
+bool __hashmap__delete(struct __hashmap *map, long key,
+ void *old_key, void *old_value);
+bool __hashmap__find(const struct __hashmap *map, long key, void *value);
+
+/*
+ * Single-loop iterators used by hashmap__for_each* so 'break' exits the entire
+ * iteration and element deletion during iteration is safe.
+ */
+struct __hashmap_iter {
+ const struct __hashmap *map;
+ size_t bkt;
+ struct hashmap_entry *cur;
+ struct hashmap_entry *next;
+};
+
+static inline struct __hashmap_iter
+__hashmap_iter__init(const struct __hashmap *map)
{
- __hashmap__clear(map);
+ return (struct __hashmap_iter){
+ .map = map,
+ .bkt = 0,
+ .cur = NULL,
+ .next = (map && map->buckets && map->cap > 0) ? map->buckets[0] : NULL,
+ };
}
-static inline void hashmap__free(struct hashmap *map)
+static inline struct hashmap_entry *
+__hashmap_iter__advance(struct __hashmap_iter *it)
{
- __hashmap__free(map);
+ if (!it->map || !it->map->buckets)
+ return NULL;
+
+ while (!it->next) {
+ it->bkt++;
+ if (it->bkt >= it->map->cap) {
+ it->cur = NULL;
+ return NULL;
+ }
+ it->next = it->map->buckets[it->bkt];
+ }
+ it->cur = it->next;
+ it->next = it->cur->next;
+ return it->cur;
}
-static inline size_t hashmap__size(const struct hashmap *map)
+struct __hashmap_key_iter {
+ const struct __hashmap *map;
+ long key;
+ struct hashmap_entry *cur;
+ struct hashmap_entry *next;
+};
+
+static inline struct __hashmap_key_iter
+__hashmap_key_iter__init(const struct __hashmap *map, long key)
{
- return __hashmap__size(map);
+ struct hashmap_entry *first = NULL;
+
+ if (map && map->buckets) {
+ size_t h = hash_bits(map->hash_fn(key, map->ctx), map->cap_bits);
+
+ first = map->buckets[h];
+ }
+ return (struct __hashmap_key_iter){
+ .map = map,
+ .key = key,
+ .cur = NULL,
+ .next = first,
+ };
}
-static inline size_t hashmap__capacity(const struct hashmap *map)
+static inline struct hashmap_entry *
+__hashmap_key_iter__advance(struct __hashmap_key_iter *it)
{
- return __hashmap__capacity(map);
+ while (it->next) {
+ struct hashmap_entry *entry = it->next;
+
+ it->next = entry->next;
+ if (it->map->equal_fn(entry->key, it->key, it->map->ctx)) {
+ it->cur = entry;
+ return entry;
+ }
+ }
+ it->cur = NULL;
+ return NULL;
}
-/*
- * Hashmap insertion strategy:
- * - 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).
+/**
+ * DECLARE_HASHMAP() - Declare a type-safe hashmap struct and alloc helpers.
+ * @name: Struct tag to define (creates 'struct @name', '@name__new()', and
+ * '@name__free()').
+ * @key_type: Type of keys stored in the map (sizeof(@key_type) <= sizeof(long)).
+ * @value_type: Type of values stored in the map
+ * (sizeof(@value_type) <= sizeof(long)).
+ *
+ * '@name__new(hash_fn, equal_fn, ctx)' allocates and initializes a new
+ * 'struct @name *', returning NULL with errno = ENOMEM on allocation failure.
+ * '@name__free(hmap)' clears all bucket storage and frees @hmap (NULL-safe;
+ * does not free caller-owned pointees referenced by keys or values).
*/
-enum hashmap_insert_strategy {
- HASHMAP_ADD,
- HASHMAP_SET,
- HASHMAP_UPDATE,
- HASHMAP_APPEND,
-};
-
-#define hashmap_cast_ptr(p) ({ \
- _Static_assert((__builtin_constant_p((p)) ? (p) == NULL : 0) || \
- sizeof(*(p)) == sizeof(long), \
- #p " pointee should be a long-sized integer or a pointer"); \
- (void *)(p); \
-})
+#define DECLARE_HASHMAP(name, key_type, value_type) \
+struct name { \
+ union { \
+ struct __hashmap map; \
+ key_type *__key; \
+ value_type *__value; \
+ int __untyped; \
+ }; \
+ _Static_assert(sizeof(key_type) <= sizeof(long), \
+ #name " key_type exceeds sizeof(long)"); \
+ _Static_assert(sizeof(value_type) <= sizeof(long), \
+ #name " value_type exceeds sizeof(long)"); \
+}; \
+static inline __maybe_unused struct name * \
+name##__new(hashmap_hash_fn hash_fn, hashmap_equal_fn equal_fn, void *ctx) \
+{ \
+ struct name *hmap = malloc(sizeof(*hmap)); \
+ \
+ if (!hmap) { \
+ errno = ENOMEM; \
+ return NULL; \
+ } \
+ __hashmap__init(&hmap->map, hash_fn, equal_fn, ctx); \
+ return hmap; \
+} \
+static inline __maybe_unused void name##__free(struct name *hmap) \
+{ \
+ if (!hmap) \
+ return; \
+ __hashmap__clear(&hmap->map); \
+ free(hmap); \
+}
/*
- * hashmap__insert() adds key/value entry w/ various semantics, depending on
- * provided strategy value. If a given key/value pair replaced already
- * existing key/value pair, both old key and old value will be returned
- * through old_key and old_value to allow calling code do proper memory
- * management.
+ * For pointer key types (T *), allow const-qualified lookup keys (const T *);
+ * for non-pointer key types, preserve the exact declared key_type.
*/
-int __hashmap__insert(struct hashmap *map, long key, long value,
- enum hashmap_insert_strategy strategy,
- void *old_key, void *old_value);
-
-#define hashmap__insert(map, key, value, strategy, old_key, old_value) \
- __hashmap__insert((map), (long)(key), (long)(value), (strategy), \
- hashmap_cast_ptr(old_key), \
- hashmap_cast_ptr(old_value))
-
-#define hashmap__add(map, key, value) \
- hashmap__insert((map), (key), (value), HASHMAP_ADD, NULL, NULL)
-
-#define hashmap__set(map, key, value, old_key, old_value) \
- hashmap__insert((map), (key), (value), HASHMAP_SET, (old_key), (old_value))
+#define ___hashmap__lookup_key_type(hm) \
+ typeof(__builtin_choose_expr( \
+ __builtin_classify_type(*(hm)->__key) == 5, \
+ (const typeof(*__builtin_choose_expr( \
+ __builtin_classify_type(*(hm)->__key) == 5, \
+ *(hm)->__key, (char *)NULL)) *)NULL, \
+ *(hm)->__key))
+
+#define ___hashmap__choose(hm, untyped_expr, typed_expr) \
+ __builtin_choose_expr(sizeof((hm)->__untyped) == 1, \
+ (untyped_expr), (typed_expr))
+
+#define ___hashmap__in_val(hm, expr) \
+ ___hashmap__choose(hm, (long)(expr), (expr))
+
+#define ___hashmap__check_untyped_ptr(hm, ptr) \
+ _Static_assert(sizeof((hm)->__untyped) != 1 || \
+ (__builtin_constant_p((ptr)) ? (ptr) == NULL : 0) || \
+ sizeof(*(ptr)) == sizeof(long), \
+ #ptr " pointee should be a long-sized integer or a pointer")
+
+/**
+ * hashmap__init() - Initialize an embedded/stack-allocated typed hashmap.
+ * @hmap: Pointer to a hashmap struct declared with DECLARE_HASHMAP().
+ * @hash_fn: Hash callback function.
+ * @equal_fn: Key equality callback function.
+ * @ctx: Optional user context pointer passed to @hash_fn and @equal_fn.
+ */
+#define hashmap__init(hmap, hash_fn, equal_fn, ctx) \
+ __hashmap__init(&(hmap)->map, (hash_fn), (equal_fn), (ctx))
-#define hashmap__update(map, key, value, old_key, old_value) \
- hashmap__insert((map), (key), (value), HASHMAP_UPDATE, (old_key), (old_value))
+/**
+ * hashmap__clear() - Remove all entries from a typed hashmap and free buckets.
+ * @hmap: Pointer to a typed hashmap.
+ *
+ * Does not free @hmap itself or caller-owned memory referenced by keys/values.
+ */
+#define hashmap__clear(hmap) \
+ __hashmap__clear(&(hmap)->map)
+
+#define ___hashmap__free(hmap, hm) \
+do { \
+ typeof(hmap) hm = (hmap); \
+ if (hm) { \
+ __hashmap__clear(&(hm)->map); \
+ free(hm); \
+ } \
+} while (0)
+
+/**
+ * hashmap__free() - Clear and free a heap-allocated typed hashmap.
+ * @hmap: Pointer to a typed hashmap allocated with <name>__new() (NULL-safe).
+ *
+ * Does not free caller-owned memory referenced by keys or values.
+ */
+#define hashmap__free(hmap) \
+ ___hashmap__free(hmap, __PASTE(__hm_, __COUNTER__))
-#define hashmap__append(map, key, value) \
- hashmap__insert((map), (key), (value), HASHMAP_APPEND, NULL, NULL)
+/**
+ * hashmap__size() - Return the number of entries currently in a hashmap.
+ * @hmap: Pointer to a typed hashmap (may be const).
+ *
+ * Return: Number of stored key/value entries.
+ */
+#define hashmap__size(hmap) \
+ __hashmap__size(&(hmap)->map)
-bool __hashmap__delete(struct hashmap *map, long key, void *old_key, void *old_value);
+/**
+ * hashmap__capacity() - Return the bucket count of a hashmap.
+ * @hmap: Pointer to a typed hashmap (may be const).
+ *
+ * Return: Number of allocated hash buckets.
+ */
+#define hashmap__capacity(hmap) \
+ __hashmap__capacity(&(hmap)->map)
+
+#define ___hashmap__insert(hmap, key, value, strategy, old_key, old_value, \
+ hm, k, v, okp, ovp, raw_ok, raw_ov, ok, ov, err) \
+({ \
+ typeof(hmap) hm = (hmap); \
+ typeof(*(hm)->__key) k = ___hashmap__in_val(hm, key); \
+ typeof(*(hm)->__value) v = ___hashmap__in_val(hm, value); \
+ long ok = 0, ov = 0; \
+ typeof((hm)->__key) okp = \
+ ___hashmap__choose(hm, (long *)NULL, (old_key)); \
+ typeof((hm)->__value) ovp = \
+ ___hashmap__choose(hm, (long *)NULL, (old_value)); \
+ void *raw_ok = ___hashmap__choose(hm, (void *)(old_key), \
+ (void *)(okp ? &ok : NULL)); \
+ void *raw_ov = ___hashmap__choose(hm, (void *)(old_value), \
+ (void *)(ovp ? &ov : NULL)); \
+ int err; \
+ ___hashmap__check_untyped_ptr(hm, old_key); \
+ ___hashmap__check_untyped_ptr(hm, old_value); \
+ err = __hashmap__insert(&(hm)->map, (long)k, (long)v, (strategy), \
+ raw_ok, raw_ov); \
+ if (okp) \
+ *okp = (typeof(*(hm)->__key))ok; \
+ if (ovp) \
+ *ovp = (typeof(*(hm)->__value))ov; \
+ err; \
+})
-#define hashmap__delete(map, key, old_key, old_value) \
- __hashmap__delete((map), (long)(key), \
- hashmap_cast_ptr(old_key), \
- hashmap_cast_ptr(old_value))
+#define _hashmap__insert(hmap, key, value, strategy, old_key, old_value, u) \
+ ___hashmap__insert(hmap, key, value, strategy, old_key, old_value, \
+ __PASTE(__hm_, u), __PASTE(__k_, u), __PASTE(__v_, u), \
+ __PASTE(__okp_, u), __PASTE(__ovp_, u), \
+ __PASTE(__rok_, u), __PASTE(__rov_, u), \
+ __PASTE(__ok_, u), __PASTE(__ov_, u), __PASTE(__err_, u))
+
+/**
+ * hashmap__insert() - Insert a key/value entry using the specified strategy.
+ * @hmap: Pointer to a typed hashmap.
+ * @key: Key to insert (must be compatible with declared key_type).
+ * @value: Value to insert (must be compatible with declared value_type).
+ * @strategy: Insertion strategy (enum hashmap_insert_strategy).
+ * @old_key: Optional pointer to key_type receiving previous key on match (or
+ * zeroed if no existing entry matched); may be NULL.
+ * @old_value: Optional pointer to value_type receiving previous value on match
+ * (or zeroed if no existing entry matched); may be NULL.
+ *
+ * If a given key/value pair replaced or matched an existing entry, both old
+ * key and old value are returned through @old_key and @old_value so calling
+ * code can perform proper memory management.
+ *
+ * Return: 0 on success, -EEXIST if @strategy is HASHMAP_ADD and @key exists,
+ * -ENOENT if @strategy is HASHMAP_UPDATE and @key is absent, or -ENOMEM on
+ * allocation failure.
+ */
+#define hashmap__insert(hmap, key, value, strategy, old_key, old_value) \
+ _hashmap__insert(hmap, key, value, strategy, old_key, old_value, __COUNTER__)
+
+/**
+ * hashmap__add() - Add a new key/value entry only if the key does not exist.
+ * @hmap: Pointer to a typed hashmap.
+ * @key: Key to insert.
+ * @value: Value to insert.
+ *
+ * Return: 0 on success, -EEXIST if @key already exists, or -ENOMEM.
+ */
+#define hashmap__add(hmap, key, value) \
+ hashmap__insert(hmap, key, value, HASHMAP_ADD, NULL, NULL)
+
+/**
+ * hashmap__set() - Add a key/value entry or replace an existing one.
+ * @hmap: Pointer to a typed hashmap.
+ * @key: Key to insert or replace.
+ * @value: Value to insert or replace.
+ * @old_key: Optional pointer to key_type receiving replaced key (or zeroed if
+ * newly inserted); may be NULL.
+ * @old_value: Optional pointer to value_type receiving replaced value (or
+ * zeroed if newly inserted); may be NULL.
+ *
+ * Return: 0 on success, or -ENOMEM on allocation failure.
+ */
+#define hashmap__set(hmap, key, value, old_key, old_value) \
+ hashmap__insert(hmap, key, value, HASHMAP_SET, old_key, old_value)
+
+/**
+ * hashmap__update() - Update value for an existing key only.
+ * @hmap: Pointer to a typed hashmap.
+ * @key: Key to look up and replace.
+ * @value: New value to store.
+ * @old_key: Optional pointer to key_type receiving replaced key; may be NULL.
+ * @old_value: Optional pointer to value_type receiving replaced value; may be
+ * NULL.
+ *
+ * Return: 0 on success, or -ENOENT if @key does not exist.
+ */
+#define hashmap__update(hmap, key, value, old_key, old_value) \
+ hashmap__insert(hmap, key, value, HASHMAP_UPDATE, old_key, old_value)
+
+/**
+ * hashmap__append() - Unconditionally append a key/value pair (multimap).
+ * @hmap: Pointer to a typed hashmap.
+ * @key: Key to append.
+ * @value: Value to append.
+ *
+ * Return: 0 on success, or -ENOMEM on allocation failure.
+ */
+#define hashmap__append(hmap, key, value) \
+ hashmap__insert(hmap, key, value, HASHMAP_APPEND, NULL, NULL)
+
+#define ___hashmap__find(hmap, key, value_ptr, hm, k, vp, raw_vp, v, found) \
+({ \
+ typeof(hmap) hm = (hmap); \
+ ___hashmap__lookup_key_type(hm) k = ___hashmap__in_val(hm, key); \
+ long v = 0; \
+ typeof((hm)->__value) vp = \
+ ___hashmap__choose(hm, (long *)NULL, (value_ptr)); \
+ void *raw_vp = ___hashmap__choose(hm, (void *)(value_ptr), \
+ (void *)(vp ? &v : NULL)); \
+ bool found; \
+ ___hashmap__check_untyped_ptr(hm, value_ptr); \
+ found = __hashmap__find(&(hm)->map, (long)k, raw_vp); \
+ if (found && vp) \
+ *vp = (typeof(*(hm)->__value))v; \
+ found; \
+})
-bool __hashmap__find(const struct hashmap *map, long key, void *value);
+#define _hashmap__find(hmap, key, value_ptr, u) \
+ ___hashmap__find(hmap, key, value_ptr, \
+ __PASTE(__hm_, u), __PASTE(__k_, u), __PASTE(__vp_, u), \
+ __PASTE(__rvp_, u), __PASTE(__v_, u), __PASTE(__found_, u))
+
+/**
+ * hashmap__find() - Look up a key in a typed hashmap.
+ * @hmap: Pointer to a typed hashmap (may be const).
+ * @key: Key to search for (const-qualified pointer keys are accepted).
+ * @value_ptr: Optional pointer to value_type receiving the found value; may be
+ * NULL to test membership only. Left unmodified if not found.
+ *
+ * Return: true if @key was found, false otherwise.
+ */
+#define hashmap__find(hmap, key, value_ptr) \
+ _hashmap__find(hmap, key, value_ptr, __COUNTER__)
+
+#define ___hashmap__delete(hmap, key, old_key, old_value, \
+ hm, k, okp, ovp, raw_ok, raw_ov, ok, ov, rem) \
+({ \
+ typeof(hmap) hm = (hmap); \
+ ___hashmap__lookup_key_type(hm) k = ___hashmap__in_val(hm, key); \
+ long ok = 0, ov = 0; \
+ typeof((hm)->__key) okp = \
+ ___hashmap__choose(hm, (long *)NULL, (old_key)); \
+ typeof((hm)->__value) ovp = \
+ ___hashmap__choose(hm, (long *)NULL, (old_value)); \
+ void *raw_ok = ___hashmap__choose(hm, (void *)(old_key), \
+ (void *)(okp ? &ok : NULL)); \
+ void *raw_ov = ___hashmap__choose(hm, (void *)(old_value), \
+ (void *)(ovp ? &ov : NULL)); \
+ bool rem; \
+ ___hashmap__check_untyped_ptr(hm, old_key); \
+ ___hashmap__check_untyped_ptr(hm, old_value); \
+ rem = __hashmap__delete(&(hm)->map, (long)k, raw_ok, raw_ov); \
+ if (okp) \
+ *okp = (typeof(*(hm)->__key))ok; \
+ if (ovp) \
+ *ovp = (typeof(*(hm)->__value))ov; \
+ rem; \
+})
-#define hashmap__find(map, key, value) \
- __hashmap__find((map), (long)(key), hashmap_cast_ptr(value))
+#define _hashmap__delete(hmap, key, old_key, old_value, u) \
+ ___hashmap__delete(hmap, key, old_key, old_value, \
+ __PASTE(__hm_, u), __PASTE(__k_, u), \
+ __PASTE(__okp_, u), __PASTE(__ovp_, u), \
+ __PASTE(__rok_, u), __PASTE(__rov_, u), \
+ __PASTE(__ok_, u), __PASTE(__ov_, u), __PASTE(__rem_, u))
+
+/**
+ * hashmap__delete() - Remove an entry matching @key from a typed hashmap.
+ * @hmap: Pointer to a typed hashmap.
+ * @key: Key to delete (const-qualified pointer keys are accepted).
+ * @old_key: Optional pointer to key_type receiving removed key (or zeroed if
+ * not found); may be NULL.
+ * @old_value: Optional pointer to value_type receiving removed value (or
+ * zeroed if not found); may be NULL.
+ *
+ * Return: true if an entry was removed, false if @key was not found.
+ */
+#define hashmap__delete(hmap, key, old_key, old_value) \
+ _hashmap__delete(hmap, key, old_key, old_value, __COUNTER__)
+
+#define ___hashmap__for_each(hmap, key_var, val_var, it, hm) \
+ for (struct __hashmap_iter it = ({ \
+ typeof(hmap) hm = (hmap); \
+ __hashmap_iter__init(hm ? &(hm)->map : NULL); \
+ }); \
+ __hashmap_iter__advance(&it) && ({ \
+ _Static_assert(sizeof(key_var) >= sizeof(*(hmap)->__key), \
+ "key_var narrower than key_type"); \
+ _Static_assert(sizeof(val_var) >= sizeof(*(hmap)->__value), \
+ "val_var narrower than value_type"); \
+ (key_var) = (typeof(*(hmap)->__key))it.cur->key; \
+ (val_var) = (typeof(*(hmap)->__value))it.cur->value; \
+ true; \
+ });)
+
+#define _hashmap__for_each(hmap, key_var, val_var, u) \
+ ___hashmap__for_each(hmap, key_var, val_var, \
+ __PASTE(__it_, u), __PASTE(__hm_, u))
+
+/**
+ * hashmap__for_each() - Iterate over all entries in a typed hashmap.
+ * @hmap: Pointer to a typed hashmap (may be const or NULL).
+ * @key_var: Loop cursor variable assigned each entry's key.
+ * @val_var: Loop cursor variable assigned each entry's value.
+ *
+ * Safe against deleting the current entry during iteration, and 'break' exits
+ * the entire loop across all buckets.
+ */
+#define hashmap__for_each(hmap, key_var, val_var) \
+ _hashmap__for_each(hmap, key_var, val_var, __COUNTER__)
+
+/**
+ * hashmap__for_each_safe() - Iterate over all entries in a typed hashmap, safe
+ * against removals.
+ * @hmap: Pointer to a typed hashmap (may be const or NULL).
+ * @key_var: Loop cursor variable assigned each entry's key.
+ * @val_var: Loop cursor variable assigned each entry's value.
+ */
+#define hashmap__for_each_safe(hmap, key_var, val_var) \
+ hashmap__for_each(hmap, key_var, val_var)
+
+#define ___hashmap__for_each_key(hmap, key, val_var, kit, hm, k) \
+ for (struct __hashmap_key_iter kit = ({ \
+ typeof(hmap) hm = (hmap); \
+ ___hashmap__lookup_key_type(hm) k = (key); \
+ __hashmap_key_iter__init(hm ? &(hm)->map : NULL, (long)k); \
+ }); \
+ __hashmap_key_iter__advance(&kit) && ({ \
+ _Static_assert(sizeof(val_var) >= sizeof(*(hmap)->__value), \
+ "val_var narrower than value_type"); \
+ (val_var) = (typeof(*(hmap)->__value))kit.cur->value; \
+ true; \
+ });)
+
+#define _hashmap__for_each_key(hmap, key, val_var, u) \
+ ___hashmap__for_each_key(hmap, key, val_var, __PASTE(__kit_, u), \
+ __PASTE(__hm_, u), __PASTE(__k_, u))
+
+/**
+ * hashmap__for_each_key() - Iterate over all values associated with @key.
+ * @hmap: Pointer to a typed hashmap (may be const or NULL).
+ * @key: Key to iterate entries for (const-qualified pointer keys accepted).
+ * @val_var: Loop cursor variable assigned each matching entry's value.
+ */
+#define hashmap__for_each_key(hmap, key, val_var) \
+ _hashmap__for_each_key(hmap, key, val_var, __COUNTER__)
+
+/**
+ * hashmap__for_each_key_safe() - Iterate over all values associated with @key,
+ * safe against removals.
+ * @hmap: Pointer to a typed hashmap (may be const or NULL).
+ * @key: Key to iterate entries for (const-qualified pointer keys accepted).
+ * @val_var: Loop cursor variable assigned each matching entry's value.
+ */
+#define hashmap__for_each_key_safe(hmap, key, val_var) \
+ hashmap__for_each_key(hmap, key, val_var)
/*
- * hashmap__for_each_entry - iterate over all entries in hashmap
- * @map: hashmap to iterate
- * @cur: struct hashmap_entry * used as a loop cursor
- * @bkt: integer used as a bucket loop cursor
+ * Legacy entry iterators for unconverted struct hashmap callers.
*/
#define hashmap__for_each_entry(map, cur, bkt) \
for (bkt = 0; bkt < (map)->cap; bkt++) \
for (cur = (map)->buckets[bkt]; cur; cur = cur->next)
-/*
- * hashmap__for_each_entry_safe - iterate over all entries in hashmap, safe
- * against removals
- * @map: hashmap to iterate
- * @cur: struct hashmap_entry * used as a loop cursor
- * @tmp: struct hashmap_entry * used as a temporary next cursor storage
- * @bkt: integer used as a bucket loop cursor
- */
#define hashmap__for_each_entry_safe(map, cur, tmp, bkt) \
for (bkt = 0; bkt < (map)->cap; bkt++) \
for (cur = (map)->buckets[bkt]; \
cur && ({tmp = cur->next; true; }); \
cur = tmp)
-/*
- * hashmap__for_each_key_entry - iterate over entries associated with given key
- * @map: hashmap to iterate
- * @cur: struct hashmap_entry * used as a loop cursor
- * @key: key to iterate entries for
- */
#define hashmap__for_each_key_entry(map, cur, _key) \
for (cur = (map)->buckets \
? (map)->buckets[hash_bits((map)->hash_fn((_key), (map)->ctx), (map)->cap_bits)] \
--
2.56.1.703.gf848ed807c-goog
next prev parent reply other threads:[~2026-11-13 21:49 UTC|newest]
Thread overview: 53+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-11-13 21:48 [PATCH v1 00/26] perf hashmap: Fix undefined behavior, remove ERR_PTR, and add type-safe generic hashmaps Ian Rogers
2026-11-13 21:48 ` [PATCH v1 01/26] perf threads: Fix uninitialized pointer put when thread removal misses Ian Rogers
2026-11-13 21:56 ` sashiko-bot
2026-11-13 21:48 ` [PATCH v1 02/26] perf annotate: Fix sym_hist_entry memory leak when zeroing histograms Ian Rogers
2026-11-13 21:59 ` sashiko-bot
2026-11-13 21:48 ` [PATCH v1 03/26] perf stat: Fix per_pkg_mask key leak on hashmap__add failure Ian Rogers
2026-11-13 21:55 ` sashiko-bot
2026-11-13 21:48 ` [PATCH v1 04/26] perf build: Don't check difference of perf and libbpf hashmap Ian Rogers
2026-11-13 21:54 ` sashiko-bot
2026-11-13 21:48 ` [PATCH v1 05/26] perf hashmap: Always build perf's hashmap and avoid libbpf symbol conflict Ian Rogers
2026-11-13 21:55 ` sashiko-bot
2026-11-13 21:48 ` [PATCH v1 06/26] perf hashmap: Fix strict aliasing violations in hashmap Ian Rogers
2026-11-13 21:56 ` sashiko-bot
2026-11-13 21:48 ` [PATCH v1 07/26] perf hashmap: Remove errptr usage from hashmap Ian Rogers
2026-11-13 22:01 ` sashiko-bot
2026-11-13 21:48 ` Ian Rogers [this message]
2026-11-13 22:01 ` [PATCH v1 08/26] perf hashmap: Introduce generic types and macros sashiko-bot
2026-11-13 21:48 ` [PATCH v1 09/26] perf fncache: Use generic hashmap for fncache Ian Rogers
2026-11-13 21:56 ` sashiko-bot
2026-11-13 21:48 ` [PATCH v1 10/26] perf ftrace: Use generic hashmap for profile_hash Ian Rogers
2026-11-13 21:57 ` sashiko-bot
2026-11-13 21:48 ` [PATCH v1 11/26] perf trace: Use generic hashmap for syscall_stats Ian Rogers
2026-11-13 22:04 ` sashiko-bot
2026-11-13 21:48 ` [PATCH v1 12/26] perf trace: Use generic hashmap for bpf-trace-summary Ian Rogers
2026-11-13 21:58 ` sashiko-bot
2026-11-13 21:48 ` [PATCH v1 13/26] perf lock: Use generic hashmap for slab_hash Ian Rogers
2026-11-13 21:57 ` sashiko-bot
2026-11-13 21:48 ` [PATCH v1 14/26] perf stat: Use generic hashmap for per_pkg_mask Ian Rogers
2026-11-13 21:57 ` sashiko-bot
2026-11-13 21:48 ` [PATCH v1 15/26] perf expr: Use generic hashmap for expr_ids Ian Rogers
2026-11-13 22:02 ` sashiko-bot
2026-11-13 21:48 ` [PATCH v1 16/26] perf pmu: Use generic hashmap for pmu aliases Ian Rogers
2026-11-13 22:04 ` sashiko-bot
2026-11-13 21:48 ` [PATCH v1 17/26] perf hwmon_pmu: Use generic hashmap for hwmon events Ian Rogers
2026-11-13 21:58 ` sashiko-bot
2026-11-13 21:48 ` [PATCH v1 18/26] perf s390: Use generic hashmap for counter name cache Ian Rogers
2026-11-13 21:59 ` sashiko-bot
2026-11-13 21:48 ` [PATCH v1 19/26] perf threads: Use generic hashmap for thread shards Ian Rogers
2026-11-13 22:01 ` sashiko-bot
2026-11-13 21:48 ` [PATCH v1 20/26] perf annotate: Use generic hashmap for sym_samples Ian Rogers
2026-11-13 22:08 ` sashiko-bot
2026-11-13 21:48 ` [PATCH v1 21/26] perf annotate: Use generic hashmap for browser type_hash Ian Rogers
2026-11-13 21:59 ` sashiko-bot
2026-11-13 21:48 ` [PATCH v1 22/26] perf annotate-data: Use generic hashmap for type_hist samples Ian Rogers
2026-11-13 22:00 ` sashiko-bot
2026-11-13 21:48 ` [PATCH v1 23/26] perf inject: Use generic hashmap for aslr remap_addresses Ian Rogers
2026-11-13 21:59 ` sashiko-bot
2026-11-13 21:48 ` [PATCH v1 24/26] perf inject: Use generic hashmap for aslr top_addresses Ian Rogers
2026-11-13 21:59 ` sashiko-bot
2026-11-13 21:48 ` [PATCH v1 25/26] perf inject: Use generic hashmap for aslr evsel_orig_attrs Ian Rogers
2026-11-13 22:00 ` sashiko-bot
2026-11-13 21:48 ` [PATCH v1 26/26] perf hashmap: Remove untyped struct hashmap API Ian Rogers
2026-11-13 22:01 ` sashiko-bot
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20261113214850.3329699-9-irogers@google.com \
--to=irogers@google.com \
--cc=acme@kernel.org \
--cc=adrian.hunter@intel.com \
--cc=james.clark@linaro.org \
--cc=jolsa@kernel.org \
--cc=linux-kernel@vger.kernel.org \
--cc=linux-perf-users@vger.kernel.org \
--cc=mingo@redhat.com \
--cc=namhyung@kernel.org \
--cc=peterz@infradead.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox