* [PATCH v1 00/26] perf hashmap: Fix undefined behavior, remove ERR_PTR, and add type-safe generic hashmaps
@ 2026-11-13 21:48 Ian Rogers
2026-11-13 21:48 ` [PATCH v1 01/26] perf threads: Fix uninitialized pointer put when thread removal misses Ian Rogers
` (25 more replies)
0 siblings, 26 replies; 53+ messages in thread
From: Ian Rogers @ 2026-11-13 21:48 UTC (permalink / raw)
To: Peter Zijlstra, Ingo Molnar, Arnaldo Carvalho de Melo,
Namhyung Kim
Cc: Jiri Olsa, Ian Rogers, Adrian Hunter, James Clark,
linux-perf-users, linux-kernel
Currently tools/perf/util/hashmap.{c,h} is a copy of libbpf's internal
untyped struct hashmap (only compiled when libbpf is disabled or
dynamically linked, and otherwise resolved against static libbpf.a).
Using that implementation in perf has several longstanding problems:
1. Strict-aliasing undefined behavior: hashmap_cast_ptr(p) casts caller
output pointers (such as void **, struct foo **, or long *) to long *
and dereferences them in hashmap_insert(), hashmap_find(), and
hashmap_delete(), violating C11 6.5/7 strict-aliasing rules [1] and
preventing perf from dropping -fno-strict-aliasing [2].
2. Non-standard ERR_PTR() returns: hashmap__new() returns
ERR_PTR(-ENOMEM) rather than NULL with errno, which differs from the
rest of user-space perf and has repeatedly caused NULL vs IS_ERR()
bugs (e.g. commits d05073adda0f, 96f202eab813, 9f3c16a430e8, and [3],
plus missing error checks in fncache and pmu fixed here).
3. No compile-time type safety and error-prone iteration/deletion: keys
and values are untyped long / void * unions requiring manual casts,
sub-long types (such as bool, int, or pid_t) cannot be passed safely
as output pointers, hashmap__for_each_entry() uses nested loops where
'break' only exits a single bucket chain rather than the whole
iteration, and hashmap__delete() leaves old_key/old_value untouched
on a miss (unlike hashmap__insert()).
This series fixes three bugs in existing hashmap callers, decouples
perf's hashmap from libbpf so strict-aliasing and ERR_PTR issues can be
fixed cleanly, introduces a zero-overhead type-safe DECLARE_HASHMAP()
generic hashmap API, migrates all 17 hashmaps across tools/perf one at a
time while keeping every commit bisectable, and removes the untyped
struct hashmap API at the end:
- Standalone bug fixes in existing callers (Patches 1-3):
- Patch 1 fixes an uninitialized pointer passed to thread__put() in
util/threads.c when hashmap__delete() misses (Fixes: 412a2ff473ac).
- Patch 2 fixes a sym_hist_entry memory leak in
symbol__annotate_zero_histograms() when clearing notes->src->samples
(Fixes: d3e7cad6f36d).
- Patch 3 fixes a per_pkg_mask key leak in check_per_pkg() when
hashmap__add() fails (Fixes: 034f7ee130c1).
- Decoupling from libbpf, strict-aliasing fix, and ERR_PTR removal
(Patches 4-7):
- Patches 4-5 drop the check-headers.sh diff check against
tools/lib/bpf/hashmap.{c,h}, always build util/hashmap.o, and move
the backing C functions to __hashmap__* so static libbpf.a builds do
not collide on global hashmap* symbols (and without perf_-specific
naming so other tools/ users can share the implementation).
- Patch 6 fixes strict-aliasing violations by passing void * output
parameters to __hashmap__insert(), __hashmap__find(), and
__hashmap__delete(), accessing them via memcpy()/memset(), and
zeroing old_key/old_value on miss in __hashmap__delete().
- Patch 7 replaces ERR_PTR(-ENOMEM) in hashmap__new() with NULL and
errno = ENOMEM, updating callers to standard NULL checks.
- Type-safe generic hashmap API and per-caller migrations (Patches 8-26):
- Patch 8 introduces DECLARE_HASHMAP(name, key_type, value_type) and
type-checked hashmap__* macros around struct __hashmap:
* Phantom pointer members (__key, __value) in a union with struct
__hashmap encode key and value types at zero runtime space cost
and without altering struct size or alignment.
* Output parameters (value_ptr, old_key, old_value) are marshaled
through long temporaries and assigned via the declared types so
sub-long types (bool, int, pid_t, etc.) are safe from stack
overflows, endianness bugs, and strict-aliasing violations, while
accepting NULL output pointers and const-qualified lookup keys.
* Single-loop hashmap__for_each{,_safe}() and
hashmap__for_each_key{,_safe}() iterators hide bucket cursors,
make in-loop deletion safe, and allow 'break' to exit the entire
iteration.
* Adds kernel-doc comments and a 'Generic hashmap' perf test suite
in tests/hashmap.c.
- Patches 9-25 migrate all 17 hashmaps in tools/perf (fncache, ftrace,
trace syscall_stats, bpf-trace-summary, bpf_lock_contention, stat
per_pkg_mask, expr_ids, pmu aliases, hwmon_pmu, s390-sample-raw,
threads, annotate sym_samples and type_hash, annotate-data, and
inject --aslr maps) one hashmap per commit.
- Patch 26 removes the transitional untyped struct hashmap wrapper and
unused hashmap__for_each_entry* macros.
[1] https://lore.kernel.org/lkml/20260321024446.692008-1-irogers@google.com/
[2] https://lore.kernel.org/lkml/20260320224248.536449-1-irogers@google.com/
[3] https://lore.kernel.org/lkml/20250805063209.3678359-1-linmq006@gmail.com/
AI Assistance Disclosure (per Documentation/process/coding-assistants.rst
and Documentation/process/generated-content.rst):
- Tools used: Google Antigravity / Jetski coding assistant.
- Summary of prompts and assistance: Interactive design, rebasing, and
refinement of the __hashmap__* backing separation, strict-aliasing and
ERR_PTR fixes, caller bug fixes, DECLARE_HASHMAP() type-safe macros,
kernel-doc comments, unit tests, and per-hashmap caller conversions.
- Testing performed: Verified all 26 commits build cleanly at every step
under both GCC (-O2 -fstrict-aliasing -Wstrict-aliasing=3) and Clang
(BUILD_NONDISTRO=1), plus NO_LIBBPF=1, DEBUG=1 (REFCNT_CHECKING=1),
and build-test targets; ran perf test (including Generic hashmap,
Simple expression parser, Sysfs/JSON PMU, Hwmon PMU, Symbols, data
type profiling, inject --aslr, stat, ftrace, and trace suites),
scripts/kernel-doc -Werror, and scripts/checkpatch.pl.
Ian Rogers (26):
perf threads: Fix uninitialized pointer put when thread removal misses
perf annotate: Fix sym_hist_entry memory leak when zeroing histograms
perf stat: Fix per_pkg_mask key leak on hashmap__add failure
perf build: Don't check difference of perf and libbpf hashmap
perf hashmap: Always build perf's hashmap and avoid libbpf symbol
conflict
perf hashmap: Fix strict aliasing violations in hashmap
perf hashmap: Remove errptr usage from hashmap
perf hashmap: Introduce generic types and macros
perf fncache: Use generic hashmap for fncache
perf ftrace: Use generic hashmap for profile_hash
perf trace: Use generic hashmap for syscall_stats
perf trace: Use generic hashmap for bpf-trace-summary
perf lock: Use generic hashmap for slab_hash
perf stat: Use generic hashmap for per_pkg_mask
perf expr: Use generic hashmap for expr_ids
perf pmu: Use generic hashmap for pmu aliases
perf hwmon_pmu: Use generic hashmap for hwmon events
perf s390: Use generic hashmap for counter name cache
perf threads: Use generic hashmap for thread shards
perf annotate: Use generic hashmap for sym_samples
perf annotate: Use generic hashmap for browser type_hash
perf annotate-data: Use generic hashmap for type_hist samples
perf inject: Use generic hashmap for aslr remap_addresses
perf inject: Use generic hashmap for aslr top_addresses
perf inject: Use generic hashmap for aslr evsel_orig_attrs
perf hashmap: Remove untyped struct hashmap API
tools/perf/builtin-ftrace.c | 52 ++-
tools/perf/builtin-trace.c | 45 +-
tools/perf/check-headers.sh | 4 -
tools/perf/tests/Build | 1 +
tools/perf/tests/builtin-test.c | 1 +
tools/perf/tests/expr.c | 2 +-
tools/perf/tests/hashmap.c | 192 ++++++++
tools/perf/tests/pmu-events.c | 16 +-
tools/perf/tests/tests.h | 1 +
tools/perf/ui/browsers/annotate.c | 22 +-
tools/perf/util/Build | 9 -
tools/perf/util/annotate-data.c | 10 +-
tools/perf/util/annotate-data.h | 5 +-
tools/perf/util/annotate.c | 30 +-
tools/perf/util/annotate.h | 13 +-
tools/perf/util/aslr.c | 48 +-
tools/perf/util/bpf-trace-summary.c | 18 +-
tools/perf/util/bpf_lock_contention.c | 12 +-
tools/perf/util/evsel.c | 10 +-
tools/perf/util/evsel.h | 6 +-
tools/perf/util/expr.c | 106 ++---
tools/perf/util/expr.h | 18 +-
tools/perf/util/expr.y | 2 +-
tools/perf/util/fncache.c | 23 +-
tools/perf/util/ftrace.h | 7 +-
tools/perf/util/hashmap.c | 136 +++---
tools/perf/util/hashmap.h | 627 ++++++++++++++++++++------
tools/perf/util/hwmon_pmu.c | 57 ++-
tools/perf/util/metricgroup.c | 28 +-
tools/perf/util/pmu.c | 36 +-
tools/perf/util/pmu.h | 7 +-
tools/perf/util/s390-sample-raw.c | 9 +-
tools/perf/util/stat.c | 13 +-
tools/perf/util/threads.c | 26 +-
tools/perf/util/threads.h | 4 +-
35 files changed, 1092 insertions(+), 504 deletions(-)
create mode 100644 tools/perf/tests/hashmap.c
base-commit: 4af65363b11fc4fe65fcb497f68bbe4f3e3a4d2c
--
2.56.1.703.gf848ed807c-goog
^ permalink raw reply [flat|nested] 53+ messages in thread
* [PATCH v1 01/26] perf threads: Fix uninitialized pointer put when thread removal misses
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 ` 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
` (24 subsequent siblings)
25 siblings, 1 reply; 53+ messages in thread
From: Ian Rogers @ 2026-11-13 21:48 UTC (permalink / raw)
To: Peter Zijlstra, Ingo Molnar, Arnaldo Carvalho de Melo,
Namhyung Kim
Cc: Jiri Olsa, Ian Rogers, Adrian Hunter, James Clark,
linux-perf-users, linux-kernel
Unlike hashmap_insert(), hashmap_delete() leaves old_key and old_value
untouched when the requested key is not present in the map. In
threads__remove() and threads__remove_all_threads(), old_value was left
uninitialized and passed unconditionally to thread__put() without
checking hashmap__delete()'s return value. If the thread is not present
in the shard, thread__put() dereferences uninitialized stack memory.
Initialize old_value to NULL and only call thread__put() when
hashmap__delete() returns true.
Fixes: 412a2ff473ac ("perf threads: Switch from rbtree to hashmap")
Signed-off-by: Ian Rogers <irogers@google.com>
---
tools/perf/util/threads.c | 12 ++++++------
1 file changed, 6 insertions(+), 6 deletions(-)
diff --git a/tools/perf/util/threads.c b/tools/perf/util/threads.c
index 6ca0b178fb6c..98cf7edaf748 100644
--- a/tools/perf/util/threads.c
+++ b/tools/perf/util/threads.c
@@ -142,10 +142,10 @@ void threads__remove_all_threads(struct threads *threads)
down_write(&table->lock);
__threads_table_entry__set_last_match(table, NULL);
hashmap__for_each_entry_safe(&table->shard, cur, tmp, bkt) {
- struct thread *old_value;
+ struct thread *old_value = NULL;
- hashmap__delete(&table->shard, cur->key, /*old_key=*/NULL, &old_value);
- thread__put(old_value);
+ if (hashmap__delete(&table->shard, cur->key, /*old_key=*/NULL, &old_value))
+ thread__put(old_value);
}
up_write(&table->lock);
}
@@ -154,14 +154,14 @@ void threads__remove_all_threads(struct threads *threads)
void threads__remove(struct threads *threads, struct thread *thread)
{
struct threads_table_entry *table = threads__table(threads, thread__tid(thread));
- struct thread *old_value;
+ struct thread *old_value = NULL;
down_write(&table->lock);
if (table->last_match && RC_CHK_EQUAL(table->last_match, thread))
__threads_table_entry__set_last_match(table, NULL);
- hashmap__delete(&table->shard, thread__tid(thread), /*old_key=*/NULL, &old_value);
- thread__put(old_value);
+ if (hashmap__delete(&table->shard, thread__tid(thread), /*old_key=*/NULL, &old_value))
+ thread__put(old_value);
up_write(&table->lock);
}
--
2.56.1.703.gf848ed807c-goog
^ permalink raw reply related [flat|nested] 53+ messages in thread
* [PATCH v1 02/26] perf annotate: Fix sym_hist_entry memory leak when zeroing histograms
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:48 ` 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
` (23 subsequent siblings)
25 siblings, 1 reply; 53+ messages in thread
From: Ian Rogers @ 2026-11-13 21:48 UTC (permalink / raw)
To: Peter Zijlstra, Ingo Molnar, Arnaldo Carvalho de Melo,
Namhyung Kim
Cc: Jiri Olsa, Ian Rogers, Adrian Hunter, James Clark,
linux-perf-users, linux-kernel
annotated_source__samples maps keys to heap-allocated struct
sym_hist_entry pointers, which annotated_source__delete() frees before
destroying the hashmap. However, symbol__annotate_zero_histograms()
called hashmap__clear(notes->src->samples) directly without freeing the
stored sym_hist_entry values, leaking every entry whenever histograms
are zeroed.
Iterate over notes->src->samples and free each entry before calling
hashmap__clear().
Fixes: d3e7cad6f36d ("perf annotate: Add a hashmap for symbol histogram")
Signed-off-by: Ian Rogers <irogers@google.com>
---
tools/perf/util/annotate.c | 9 ++++++++-
1 file changed, 8 insertions(+), 1 deletion(-)
diff --git a/tools/perf/util/annotate.c b/tools/perf/util/annotate.c
index 86a4beec6528..9af1d6b72da9 100644
--- a/tools/perf/util/annotate.c
+++ b/tools/perf/util/annotate.c
@@ -164,7 +164,14 @@ void symbol__annotate_zero_histograms(struct symbol *sym)
if (notes->src != NULL) {
memset(notes->src->histograms, 0,
notes->src->nr_histograms * sizeof(*notes->src->histograms));
- hashmap__clear(notes->src->samples);
+ if (notes->src->samples) {
+ struct hashmap_entry *cur;
+ size_t bkt;
+
+ hashmap__for_each_entry(notes->src->samples, cur, bkt)
+ zfree(&cur->pvalue);
+ hashmap__clear(notes->src->samples);
+ }
}
if (notes->branch && notes->branch->cycles_hist) {
memset(notes->branch->cycles_hist, 0,
--
2.56.1.703.gf848ed807c-goog
^ permalink raw reply related [flat|nested] 53+ messages in thread
* [PATCH v1 03/26] perf stat: Fix per_pkg_mask key leak on hashmap__add failure
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:48 ` [PATCH v1 02/26] perf annotate: Fix sym_hist_entry memory leak when zeroing histograms Ian Rogers
@ 2026-11-13 21:48 ` 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
` (22 subsequent siblings)
25 siblings, 1 reply; 53+ messages in thread
From: Ian Rogers @ 2026-11-13 21:48 UTC (permalink / raw)
To: Peter Zijlstra, Ingo Molnar, Arnaldo Carvalho de Melo,
Namhyung Kim
Cc: Jiri Olsa, Ian Rogers, Adrian Hunter, James Clark,
linux-perf-users, linux-kernel
In check_per_pkg(), key is allocated with malloc() before checking
whether the (die, socket) pair is already present in per_pkg_mask. If
hashmap__find() misses and hashmap__add() fails (e.g. -ENOMEM), key is
never freed.
Free key when hashmap__add() fails.
Fixes: 034f7ee130c1 ("perf stat: Fix wrong skipping for per-die aggregation")
Signed-off-by: Ian Rogers <irogers@google.com>
---
tools/perf/util/stat.c | 5 ++++-
1 file changed, 4 insertions(+), 1 deletion(-)
diff --git a/tools/perf/util/stat.c b/tools/perf/util/stat.c
index 6da3dbbde0e2..e7013449271d 100644
--- a/tools/perf/util/stat.c
+++ b/tools/perf/util/stat.c
@@ -363,8 +363,11 @@ static int check_per_pkg(struct evsel *counter, struct perf_counts_values *vals,
if (hashmap__find(mask, key, NULL)) {
*skip = true;
free(key);
- } else
+ } else {
ret = hashmap__add(mask, key, 1);
+ if (ret)
+ free(key);
+ }
return ret;
}
--
2.56.1.703.gf848ed807c-goog
^ permalink raw reply related [flat|nested] 53+ messages in thread
* [PATCH v1 04/26] perf build: Don't check difference of perf and libbpf hashmap
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
` (2 preceding siblings ...)
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:48 ` 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
` (21 subsequent siblings)
25 siblings, 1 reply; 53+ messages in thread
From: Ian Rogers @ 2026-11-13 21:48 UTC (permalink / raw)
To: Peter Zijlstra, Ingo Molnar, Arnaldo Carvalho de Melo,
Namhyung Kim
Cc: Jiri Olsa, Ian Rogers, Adrian Hunter, James Clark,
linux-perf-users, linux-kernel
We want to explore diverging the perf hashmap from libbpf's to fix a
few subtle bugs around strict aliasing (casting void ** to long *) and
use of ERR_PTR which is non-standard in the perf codebase and a source
of bugs such as:
commit d05073adda0f ("perf trace: Avoid an ERR_PTR in syscall_stats")
commit 96f202eab813 ("perf trace: Fix IS_ERR() vs NULL check bug")
commit 9f3c16a430e8 ("perf expr: Fix return value of ids__new()")
Signed-off-by: Ian Rogers <irogers@google.com>
---
| 4 ----
1 file changed, 4 deletions(-)
--git a/tools/perf/check-headers.sh b/tools/perf/check-headers.sh
index 531c0e0e84df..846a1b2fa5d8 100755
--- a/tools/perf/check-headers.sh
+++ b/tools/perf/check-headers.sh
@@ -214,10 +214,6 @@ do
beauty_check "$i" -B
done
-# check duplicated library files
-check_2 tools/perf/util/hashmap.h tools/lib/bpf/hashmap.h
-check_2 tools/perf/util/hashmap.c tools/lib/bpf/hashmap.c
-
# Files with larger differences
check_ignore_some_hunks lib/list_sort.c
--
2.56.1.703.gf848ed807c-goog
^ permalink raw reply related [flat|nested] 53+ messages in thread
* [PATCH v1 05/26] perf hashmap: Always build perf's hashmap and avoid libbpf symbol conflict
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
` (3 preceding siblings ...)
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:48 ` 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
` (20 subsequent siblings)
25 siblings, 1 reply; 53+ messages in thread
From: Ian Rogers @ 2026-11-13 21:48 UTC (permalink / raw)
To: Peter Zijlstra, Ingo Molnar, Arnaldo Carvalho de Melo,
Namhyung Kim
Cc: Jiri Olsa, Ian Rogers, Adrian Hunter, James Clark,
linux-perf-users, linux-kernel
Previously tools/perf/util/hashmap.o was only compiled when libbpf was
disabled or linked dynamically; with static libbpf.a, perf linked
against libbpf's internal hashmap symbols.
To allow perf's hashmap implementation to diverge from libbpf's without
colliding with libbpf.a's global hashmap symbols or churning callers:
- Always build util/hashmap.o in tools/perf/util/Build.
- Rename the backing C functions in util/hashmap.c to __hashmap__*
(__hashmap__init, __hashmap__new, __hashmap__clear, __hashmap__free,
__hashmap__size, __hashmap__capacity, __hashmap__insert,
__hashmap__find, and __hashmap__delete, along with the internal
__hashmap__* helpers).
- Keep the existing struct hashmap and hashmap__* header wrappers in
util/hashmap.h forwarding to those functions, and update the single
direct hashmap_find() call in hwmon_pmu.c to hashmap__find().
Signed-off-by: Ian Rogers <irogers@google.com>
---
tools/perf/util/Build | 9 ----
tools/perf/util/hashmap.c | 70 +++++++++++++++----------------
tools/perf/util/hashmap.h | 83 ++++++++++++++++++++++++++-----------
tools/perf/util/hwmon_pmu.c | 2 +-
4 files changed, 94 insertions(+), 70 deletions(-)
diff --git a/tools/perf/util/Build b/tools/perf/util/Build
index 2c1f880c4c47..d9090f217301 100644
--- a/tools/perf/util/Build
+++ b/tools/perf/util/Build
@@ -197,16 +197,7 @@ perf-util-$(CONFIG_LIBELF) += symbol-elf.o
perf-util-$(CONFIG_LIBELF) += probe-file.o
perf-util-$(CONFIG_LIBELF) += probe-event.o
-ifdef CONFIG_LIBBPF_DYNAMIC
- hashmap := 1
-endif
-ifndef CONFIG_LIBBPF
- hashmap := 1
-endif
-
-ifdef hashmap
perf-util-y += hashmap.o
-endif
ifndef CONFIG_LIBELF
perf-util-y += symbol-minimal.o
diff --git a/tools/perf/util/hashmap.c b/tools/perf/util/hashmap.c
index 140ee4055676..b6f80a92f5dd 100644
--- a/tools/perf/util/hashmap.c
+++ b/tools/perf/util/hashmap.c
@@ -21,22 +21,22 @@
/* start with 4 buckets */
#define HASHMAP_MIN_CAP_BITS 2
-static void hashmap_add_entry(struct hashmap_entry **pprev,
- struct hashmap_entry *entry)
+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)
+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)
+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;
@@ -48,19 +48,19 @@ 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));
if (!map)
return ERR_PTR(-ENOMEM);
- hashmap__init(map, hash_fn, equal_fn, ctx);
+ __hashmap__init(map, hash_fn, equal_fn, ctx);
return map;
}
-void hashmap__clear(struct hashmap *map)
+void __hashmap__clear(struct hashmap *map)
{
struct hashmap_entry *cur, *tmp;
size_t bkt;
@@ -73,32 +73,32 @@ 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 (IS_ERR_OR_NULL(map))
return;
- hashmap__clear(map);
+ __hashmap__clear(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;
@@ -116,7 +116,7 @@ static int hashmap_grow(struct hashmap *map)
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);
+ __hashmap__add_entry(&new_buckets[h], cur);
}
map->cap = new_cap;
@@ -127,10 +127,10 @@ static int hashmap_grow(struct hashmap *map)
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)
+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;
@@ -151,9 +151,9 @@ static bool hashmap_find_entry(const struct hashmap *map,
return false;
}
-int hashmap_insert(struct hashmap *map, long key, long value,
- enum hashmap_insert_strategy strategy,
- long *old_key, long *old_value)
+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;
@@ -166,7 +166,7 @@ int hashmap_insert(struct hashmap *map, long key, long value,
h = hash_bits(map->hash_fn(key, map->ctx), map->cap_bits);
if (strategy != HASHMAP_APPEND &&
- hashmap_find_entry(map, key, h, NULL, &entry)) {
+ __hashmap__find_entry(map, key, h, NULL, &entry)) {
if (old_key)
*old_key = entry->key;
if (old_value)
@@ -184,8 +184,8 @@ int hashmap_insert(struct hashmap *map, long key, long value,
if (strategy == HASHMAP_UPDATE)
return -ENOENT;
- if (hashmap_needs_to_grow(map)) {
- err = hashmap_grow(map);
+ 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);
@@ -197,19 +197,19 @@ int hashmap_insert(struct hashmap *map, long key, long value,
entry->key = key;
entry->value = value;
- hashmap_add_entry(&map->buckets[h], entry);
+ __hashmap__add_entry(&map->buckets[h], entry);
map->sz++;
return 0;
}
-bool hashmap_find(const struct hashmap *map, long key, long *value)
+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))
+ if (!__hashmap__find_entry(map, key, h, NULL, &entry))
return false;
if (value)
@@ -217,14 +217,14 @@ bool hashmap_find(const struct hashmap *map, long key, long *value)
return true;
}
-bool hashmap_delete(struct hashmap *map, long key,
- long *old_key, long *old_value)
+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))
+ if (!__hashmap__find_entry(map, key, h, &pprev, &entry))
return false;
if (old_key)
@@ -232,7 +232,7 @@ bool hashmap_delete(struct hashmap *map, long key,
if (old_value)
*old_value = entry->value;
- hashmap_del_entry(pprev, entry);
+ __hashmap__del_entry(pprev, entry);
free(entry);
map->sz--;
diff --git a/tools/perf/util/hashmap.h b/tools/perf/util/hashmap.h
index 0c4f155e8eb7..f8bc70a33026 100644
--- a/tools/perf/util/hashmap.h
+++ b/tools/perf/util/hashmap.h
@@ -5,8 +5,8 @@
*
* Copyright (c) 2019 Facebook
*/
-#ifndef __LIBBPF_HASHMAP_H
-#define __LIBBPF_HASHMAP_H
+#ifndef __PERF_UTIL_HASHMAP_H
+#define __PERF_UTIL_HASHMAP_H
#include <stdbool.h>
#include <stddef.h>
@@ -80,16 +80,49 @@ 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);
+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);
-size_t hashmap__size(const struct hashmap *map);
-size_t hashmap__capacity(const struct hashmap *map);
+size_t __hashmap__size(const struct hashmap *map);
+size_t __hashmap__capacity(const 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);
+}
+
+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);
+}
+
+static inline void hashmap__clear(struct hashmap *map)
+{
+ __hashmap__clear(map);
+}
+
+static inline void hashmap__free(struct hashmap *map)
+{
+ __hashmap__free(map);
+}
+
+static inline size_t hashmap__size(const struct hashmap *map)
+{
+ return __hashmap__size(map);
+}
+
+static inline size_t hashmap__capacity(const struct hashmap *map)
+{
+ return __hashmap__capacity(map);
+}
/*
* Hashmap insertion strategy:
@@ -126,14 +159,14 @@ enum hashmap_insert_strategy {
* through old_key and old_value to allow calling code do proper memory
* management.
*/
-int hashmap_insert(struct hashmap *map, long key, long value,
- enum hashmap_insert_strategy strategy,
- long *old_key, long *old_value);
+int __hashmap__insert(struct hashmap *map, long key, long value,
+ enum hashmap_insert_strategy strategy,
+ long *old_key, long *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))
+ __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)
@@ -147,17 +180,17 @@ int hashmap_insert(struct hashmap *map, long key, long value,
#define hashmap__append(map, key, value) \
hashmap__insert((map), (key), (value), HASHMAP_APPEND, NULL, NULL)
-bool hashmap_delete(struct hashmap *map, long key, long *old_key, long *old_value);
+bool __hashmap__delete(struct hashmap *map, long key, long *old_key, long *old_value);
-#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__delete(map, key, old_key, old_value) \
+ __hashmap__delete((map), (long)(key), \
+ hashmap_cast_ptr(old_key), \
+ hashmap_cast_ptr(old_value))
-bool hashmap_find(const struct hashmap *map, long key, long *value);
+bool __hashmap__find(const struct hashmap *map, long key, long *value);
#define hashmap__find(map, key, value) \
- hashmap_find((map), (long)(key), hashmap_cast_ptr(value))
+ __hashmap__find((map), (long)(key), hashmap_cast_ptr(value))
/*
* hashmap__for_each_entry - iterate over all entries in hashmap
@@ -205,4 +238,4 @@ bool hashmap_find(const struct hashmap *map, long key, long *value);
cur = tmp) \
if ((map)->equal_fn(cur->key, (_key), (map)->ctx))
-#endif /* __LIBBPF_HASHMAP_H */
+#endif /* __PERF_UTIL_HASHMAP_H */
diff --git a/tools/perf/util/hwmon_pmu.c b/tools/perf/util/hwmon_pmu.c
index 96aeb379e169..c150fc0d7bd3 100644
--- a/tools/perf/util/hwmon_pmu.c
+++ b/tools/perf/util/hwmon_pmu.c
@@ -571,7 +571,7 @@ bool hwmon_pmu__have_event(struct perf_pmu *pmu, const char *name)
key.type = type;
key.num = number;
- if (hashmap_find(&hwm->events, key.type_and_num, /*value=*/NULL))
+ if (hashmap__find(&hwm->events, key.type_and_num, /*value=*/NULL))
return true;
if (key.num != -1)
return false;
--
2.56.1.703.gf848ed807c-goog
^ permalink raw reply related [flat|nested] 53+ messages in thread
* [PATCH v1 06/26] perf hashmap: Fix strict aliasing violations in hashmap
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
` (4 preceding siblings ...)
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:48 ` 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
` (19 subsequent siblings)
25 siblings, 1 reply; 53+ messages in thread
From: Ian Rogers @ 2026-11-13 21:48 UTC (permalink / raw)
To: Peter Zijlstra, Ingo Molnar, Arnaldo Carvalho de Melo,
Namhyung Kim
Cc: Jiri Olsa, Ian Rogers, Adrian Hunter, James Clark,
linux-perf-users, linux-kernel
The hashmap in perf util (copied from libbpf) contained strict
aliasing violations. Specifically, the hashmap_cast_ptr(p) macro was
casting pointers (such as void **) to long *, and these were
subsequently dereferenced in functions like __hashmap__insert(),
__hashmap__find(), and __hashmap__delete().
C's strict aliasing rules (C11 6.5/7) prohibit accessing an object
through an lvalue of an incompatible type. Dereferencing a long * to
write to a void * object is a violation, even if they share the same
size, as they are not compatible types. This can lead to undefined
behavior, especially with aggressive compiler optimizations.
Fix this by:
1. Updating __hashmap__insert(), __hashmap__find(), and
__hashmap__delete() to take void * for their output parameters
(old_key, old_value, and value), and zeroing old_key/old_value on
entry in __hashmap__delete() to match __hashmap__insert().
2. Modifying the implementation to use memcpy() and memset() for
accessing these output parameters. Accessing an object as an array of
characters (as done by memcpy) is a permitted exception to the
strict aliasing rules.
3. Updating the hashmap_cast_ptr(p) macro to return void *, ensuring
compatibility with the new function signatures while preserving the
static assertion that ensures the pointed-to type matches the size of
a long.
Input parameters (key and value) remain as long, as they involve value
conversion rather than incompatible pointer dereferencing, which is safe
under strict aliasing rules.
Signed-off-by: Ian Rogers <irogers@google.com>
---
tools/perf/util/hashmap.c | 26 ++++++++++++++++----------
tools/perf/util/hashmap.h | 8 ++++----
2 files changed, 20 insertions(+), 14 deletions(-)
diff --git a/tools/perf/util/hashmap.c b/tools/perf/util/hashmap.c
index b6f80a92f5dd..7fd87c7d72be 100644
--- a/tools/perf/util/hashmap.c
+++ b/tools/perf/util/hashmap.c
@@ -8,6 +8,7 @@
#include <stdint.h>
#include <stdlib.h>
#include <stdio.h>
+#include <string.h>
#include <errno.h>
#include <linux/err.h>
#include "hashmap.h"
@@ -153,24 +154,24 @@ static bool __hashmap__find_entry(const struct hashmap *map,
int __hashmap__insert(struct hashmap *map, long key, long value,
enum hashmap_insert_strategy strategy,
- long *old_key, long *old_value)
+ void *old_key, void *old_value)
{
struct hashmap_entry *entry;
size_t h;
int err;
if (old_key)
- *old_key = 0;
+ memset(old_key, 0, sizeof(long));
if (old_value)
- *old_value = 0;
+ memset(old_value, 0, sizeof(long));
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;
+ memcpy(old_key, &entry->key, sizeof(long));
if (old_value)
- *old_value = entry->value;
+ memcpy(old_value, &entry->value, sizeof(long));
if (strategy == HASHMAP_SET || strategy == HASHMAP_UPDATE) {
entry->key = key;
@@ -203,7 +204,7 @@ int __hashmap__insert(struct hashmap *map, long key, long value,
return 0;
}
-bool __hashmap__find(const struct hashmap *map, long key, long *value)
+bool __hashmap__find(const struct hashmap *map, long key, void *value)
{
struct hashmap_entry *entry;
size_t h;
@@ -213,24 +214,29 @@ bool __hashmap__find(const struct hashmap *map, long key, long *value)
return false;
if (value)
- *value = entry->value;
+ memcpy(value, &entry->value, sizeof(long));
return true;
}
bool __hashmap__delete(struct hashmap *map, long key,
- long *old_key, long *old_value)
+ void *old_key, void *old_value)
{
struct hashmap_entry **pprev, *entry;
size_t h;
+ if (old_key)
+ memset(old_key, 0, sizeof(long));
+ if (old_value)
+ memset(old_value, 0, sizeof(long));
+
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;
+ memcpy(old_key, &entry->key, sizeof(long));
if (old_value)
- *old_value = entry->value;
+ memcpy(old_value, &entry->value, sizeof(long));
__hashmap__del_entry(pprev, entry);
free(entry);
diff --git a/tools/perf/util/hashmap.h b/tools/perf/util/hashmap.h
index f8bc70a33026..4dcc2cf3ef1a 100644
--- a/tools/perf/util/hashmap.h
+++ b/tools/perf/util/hashmap.h
@@ -149,7 +149,7 @@ enum hashmap_insert_strategy {
_Static_assert((__builtin_constant_p((p)) ? (p) == NULL : 0) || \
sizeof(*(p)) == sizeof(long), \
#p " pointee should be a long-sized integer or a pointer"); \
- (long *)(p); \
+ (void *)(p); \
})
/*
@@ -161,7 +161,7 @@ enum hashmap_insert_strategy {
*/
int __hashmap__insert(struct hashmap *map, long key, long value,
enum hashmap_insert_strategy strategy,
- long *old_key, long *old_value);
+ 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), \
@@ -180,14 +180,14 @@ int __hashmap__insert(struct hashmap *map, long key, long value,
#define hashmap__append(map, key, value) \
hashmap__insert((map), (key), (value), HASHMAP_APPEND, NULL, NULL)
-bool __hashmap__delete(struct hashmap *map, long key, long *old_key, long *old_value);
+bool __hashmap__delete(struct hashmap *map, long key, void *old_key, void *old_value);
#define hashmap__delete(map, key, old_key, old_value) \
__hashmap__delete((map), (long)(key), \
hashmap_cast_ptr(old_key), \
hashmap_cast_ptr(old_value))
-bool __hashmap__find(const struct hashmap *map, long key, long *value);
+bool __hashmap__find(const struct hashmap *map, long key, void *value);
#define hashmap__find(map, key, value) \
__hashmap__find((map), (long)(key), hashmap_cast_ptr(value))
--
2.56.1.703.gf848ed807c-goog
^ permalink raw reply related [flat|nested] 53+ messages in thread
* [PATCH v1 07/26] perf hashmap: Remove errptr usage from hashmap
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
` (5 preceding siblings ...)
2026-11-13 21:48 ` [PATCH v1 06/26] perf hashmap: Fix strict aliasing violations in hashmap Ian Rogers
@ 2026-11-13 21:48 ` Ian Rogers
2026-11-13 22:01 ` sashiko-bot
2026-11-13 21:48 ` [PATCH v1 08/26] perf hashmap: Introduce generic types and macros Ian Rogers
` (18 subsequent siblings)
25 siblings, 1 reply; 53+ messages in thread
From: Ian Rogers @ 2026-11-13 21:48 UTC (permalink / raw)
To: Peter Zijlstra, Ingo Molnar, Arnaldo Carvalho de Melo,
Namhyung Kim
Cc: Jiri Olsa, Ian Rogers, Adrian Hunter, James Clark,
linux-perf-users, linux-kernel
The hashmap implementation in tools/perf/util was using ERR_PTR to
return errors from hashmap__new(). This is non-standard for most of
the perf codebase which prefers NULL on error and setting errno. As
such it was a frequent source of bugs:
commit d05073adda0f ("perf trace: Avoid an ERR_PTR in syscall_stats")
commit 96f202eab813 ("perf trace: Fix IS_ERR() vs NULL check bug")
commit 9f3c16a430e8 ("perf expr: Fix return value of ids__new()")
commit 4d4d00dd321f ("perf tools: Update copy of libbpf's hashmap.c")
Remove the dependency on linux/err.h in hashmap.c. Update
__hashmap__new() to set errno = ENOMEM and return NULL on failure, and
update __hashmap__free() to check for NULL instead of using
IS_ERR_OR_NULL().
Update all callers of hashmap__new() to check for NULL (including
missing failure checks in fncache and
perf_pmu__create_placeholder_core_pmu()), and switch IS_ERR_OR_NULL()
checks on hashmap pointers to regular NULL checks.
Signed-off-by: Ian Rogers <irogers@google.com>
---
tools/perf/builtin-ftrace.c | 9 +--------
tools/perf/builtin-trace.c | 4 +---
tools/perf/ui/browsers/annotate.c | 4 ++--
tools/perf/util/annotate.c | 4 +---
tools/perf/util/expr.c | 9 ++-------
tools/perf/util/fncache.c | 10 +++++++---
tools/perf/util/hashmap.c | 9 +++++----
tools/perf/util/pmu.c | 6 ++++++
tools/perf/util/s390-sample-raw.c | 2 +-
tools/perf/util/stat.c | 2 +-
10 files changed, 27 insertions(+), 32 deletions(-)
diff --git a/tools/perf/builtin-ftrace.c b/tools/perf/builtin-ftrace.c
index 7cdc9ae2fbbb..ac0107f5b891 100644
--- a/tools/perf/builtin-ftrace.c
+++ b/tools/perf/builtin-ftrace.c
@@ -1229,14 +1229,7 @@ static int prepare_func_profile(struct perf_ftrace *ftrace)
ftrace->graph_verbose = 0;
ftrace->profile_hash = hashmap__new(profile_hash, profile_equal, NULL);
- if (IS_ERR(ftrace->profile_hash)) {
- int err = PTR_ERR(ftrace->profile_hash);
-
- ftrace->profile_hash = NULL;
- return err;
- }
-
- return 0;
+ return ftrace->profile_hash ? 0 : -ENOMEM;
}
/* This is saved in a hashmap keyed by the function name */
diff --git a/tools/perf/builtin-trace.c b/tools/perf/builtin-trace.c
index 4094141461f9..128efe1be8c3 100644
--- a/tools/perf/builtin-trace.c
+++ b/tools/perf/builtin-trace.c
@@ -1673,9 +1673,7 @@ static bool syscall_id_equal(long key1, long key2, void *ctx __maybe_unused)
static struct hashmap *alloc_syscall_stats(void)
{
- struct hashmap *result = hashmap__new(syscall_id_hash, syscall_id_equal, NULL);
-
- return IS_ERR(result) ? NULL : result;
+ return hashmap__new(syscall_id_hash, syscall_id_equal, NULL);
}
static void delete_syscall_stats(struct hashmap *syscall_stats)
diff --git a/tools/perf/ui/browsers/annotate.c b/tools/perf/ui/browsers/annotate.c
index cf36b5347f72..8b0c933caf2e 100644
--- a/tools/perf/ui/browsers/annotate.c
+++ b/tools/perf/ui/browsers/annotate.c
@@ -144,7 +144,7 @@ static void annotate_browser__write(struct ui_browser *browser, void *entry, int
if (!browser->navkeypressed)
ops.width += 1;
- if (!IS_ERR_OR_NULL(ab->type_hash))
+ if (ab->type_hash)
apd.type_hash = ab->type_hash;
annotation_line__write(al, notes, &ops, &apd);
@@ -1264,7 +1264,7 @@ int __hist_entry__tui_annotate(struct hist_entry *he, struct map_symbol *ms,
debuginfo__delete(browser.dbg);
- if (!IS_ERR_OR_NULL(browser.type_hash)) {
+ if (browser.type_hash) {
struct hashmap_entry *cur;
size_t bkt;
diff --git a/tools/perf/util/annotate.c b/tools/perf/util/annotate.c
index 9af1d6b72da9..d11add6424dc 100644
--- a/tools/perf/util/annotate.c
+++ b/tools/perf/util/annotate.c
@@ -137,10 +137,8 @@ static int annotated_source__alloc_histograms(struct annotated_source *src,
return -1;
src->samples = hashmap__new(sym_hist_hash, sym_hist_equal, NULL);
- if (IS_ERR(src->samples)) {
+ if (!src->samples)
zfree(&src->histograms);
- src->samples = NULL;
- }
return src->histograms ? 0 : -1;
}
diff --git a/tools/perf/util/expr.c b/tools/perf/util/expr.c
index f1164c4666b1..ac0243576def 100644
--- a/tools/perf/util/expr.c
+++ b/tools/perf/util/expr.c
@@ -66,12 +66,7 @@ static bool key_equal(long key1, long key2, void *ctx __maybe_unused)
struct hashmap *ids__new(void)
{
- struct hashmap *hash;
-
- hash = hashmap__new(key_hash, key_equal, NULL);
- if (IS_ERR(hash))
- return NULL;
- return hash;
+ return hashmap__new(key_hash, key_equal, NULL);
}
void ids__free(struct hashmap *ids)
@@ -306,7 +301,7 @@ struct expr_parse_ctx *expr__ctx_new(void)
return NULL;
ctx->ids = hashmap__new(key_hash, key_equal, NULL);
- if (IS_ERR(ctx->ids)) {
+ if (!ctx->ids) {
free(ctx);
return NULL;
}
diff --git a/tools/perf/util/fncache.c b/tools/perf/util/fncache.c
index bf9559c55c63..04ea150bc7cd 100644
--- a/tools/perf/util/fncache.c
+++ b/tools/perf/util/fncache.c
@@ -36,9 +36,10 @@ static struct hashmap *fncache__get(void)
static bool lookup_fncache(const char *name, bool *res)
{
+ struct hashmap *map = fncache__get();
long val;
- if (!hashmap__find(fncache__get(), name, &val))
+ if (!map || !hashmap__find(map, name, &val))
return false;
*res = (val != 0);
@@ -47,11 +48,14 @@ static bool lookup_fncache(const char *name, bool *res)
static void update_fncache(const char *name, bool res)
{
+ struct hashmap *map = fncache__get();
char *old_key = NULL, *key = strdup(name);
- if (key) {
- hashmap__set(fncache__get(), key, res, &old_key, /*old_value*/NULL);
+ if (map && key) {
+ hashmap__set(map, key, res, &old_key, /*old_value*/NULL);
free(old_key);
+ } else {
+ free(key);
}
}
diff --git a/tools/perf/util/hashmap.c b/tools/perf/util/hashmap.c
index 7fd87c7d72be..f2d8beeac27a 100644
--- a/tools/perf/util/hashmap.c
+++ b/tools/perf/util/hashmap.c
@@ -10,7 +10,6 @@
#include <stdio.h>
#include <string.h>
#include <errno.h>
-#include <linux/err.h>
#include "hashmap.h"
/* make sure libbpf doesn't use kernel-only integer typedefs */
@@ -55,8 +54,10 @@ struct hashmap *__hashmap__new(hashmap_hash_fn hash_fn,
{
struct hashmap *map = malloc(sizeof(struct hashmap));
- if (!map)
- return ERR_PTR(-ENOMEM);
+ if (!map) {
+ errno = ENOMEM;
+ return NULL;
+ }
__hashmap__init(map, hash_fn, equal_fn, ctx);
return map;
}
@@ -76,7 +77,7 @@ void __hashmap__clear(struct hashmap *map)
void __hashmap__free(struct hashmap *map)
{
- if (IS_ERR_OR_NULL(map))
+ if (!map)
return;
__hashmap__clear(map);
diff --git a/tools/perf/util/pmu.c b/tools/perf/util/pmu.c
index 01753d5c69d1..a63fa0d9cfa5 100644
--- a/tools/perf/util/pmu.c
+++ b/tools/perf/util/pmu.c
@@ -1314,6 +1314,12 @@ struct perf_pmu *perf_pmu__create_placeholder_core_pmu(struct list_head *core_pm
INIT_LIST_HEAD(&pmu->format);
pmu->aliases = hashmap__new(aliases__hash, aliases__equal, /*ctx=*/ NULL);
+ if (!pmu->aliases) {
+ perf_cpu_map__put(pmu->cpus);
+ free((char *)pmu->name);
+ free(pmu);
+ return NULL;
+ }
INIT_LIST_HEAD(&pmu->caps);
list_add_tail(&pmu->list, core_pmus);
return pmu;
diff --git a/tools/perf/util/s390-sample-raw.c b/tools/perf/util/s390-sample-raw.c
index aaf8838b2cb2..e0fac2aa15ed 100644
--- a/tools/perf/util/s390-sample-raw.c
+++ b/tools/perf/util/s390-sample-raw.c
@@ -202,7 +202,7 @@ static char *get_counter_name(int set, int nr, struct perf_pmu *pmu)
get_counter_name_hashmap_equal_fn,
/*ctx=*/NULL);
- if (!IS_ERR(tmp)) {
+ if (tmp) {
cache = tmp;
cache_pmu = pmu;
}
diff --git a/tools/perf/util/stat.c b/tools/perf/util/stat.c
index e7013449271d..097985da3dbb 100644
--- a/tools/perf/util/stat.c
+++ b/tools/perf/util/stat.c
@@ -326,7 +326,7 @@ static int check_per_pkg(struct evsel *counter, struct perf_counts_values *vals,
if (!mask) {
mask = hashmap__new(pkg_id_hash, pkg_id_equal, NULL);
- if (IS_ERR(mask))
+ if (!mask)
return -ENOMEM;
counter->per_pkg_mask = mask;
--
2.56.1.703.gf848ed807c-goog
^ permalink raw reply related [flat|nested] 53+ messages in thread
* [PATCH v1 08/26] perf hashmap: Introduce generic types and macros
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
` (6 preceding siblings ...)
2026-11-13 21:48 ` [PATCH v1 07/26] perf hashmap: Remove errptr usage from hashmap Ian Rogers
@ 2026-11-13 21:48 ` Ian Rogers
2026-11-13 22:01 ` sashiko-bot
2026-11-13 21:48 ` [PATCH v1 09/26] perf fncache: Use generic hashmap for fncache Ian Rogers
` (17 subsequent siblings)
25 siblings, 1 reply; 53+ messages in thread
From: Ian Rogers @ 2026-11-13 21:48 UTC (permalink / raw)
To: Peter Zijlstra, Ingo Molnar, Arnaldo Carvalho de Melo,
Namhyung Kim
Cc: Jiri Olsa, Ian Rogers, Adrian Hunter, James Clark,
linux-perf-users, linux-kernel
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
^ permalink raw reply related [flat|nested] 53+ messages in thread
* [PATCH v1 09/26] perf fncache: Use generic hashmap for fncache
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
` (7 preceding siblings ...)
2026-11-13 21:48 ` [PATCH v1 08/26] perf hashmap: Introduce generic types and macros Ian Rogers
@ 2026-11-13 21:48 ` 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
` (16 subsequent siblings)
25 siblings, 1 reply; 53+ messages in thread
From: Ian Rogers @ 2026-11-13 21:48 UTC (permalink / raw)
To: Peter Zijlstra, Ingo Molnar, Arnaldo Carvalho de Melo,
Namhyung Kim
Cc: Jiri Olsa, Ian Rogers, Adrian Hunter, James Clark,
linux-perf-users, linux-kernel
Convert fncache in tools/perf/util/fncache.c to
DECLARE_HASHMAP(fncache_map, char *, bool) and use the type-checked
hashmap__* wrappers, eliminating the manual long temporary conversion on
bool lookups.
Signed-off-by: Ian Rogers <irogers@google.com>
---
tools/perf/util/fncache.c | 21 +++++++++------------
1 file changed, 9 insertions(+), 12 deletions(-)
diff --git a/tools/perf/util/fncache.c b/tools/perf/util/fncache.c
index 04ea150bc7cd..f00367b5f265 100644
--- a/tools/perf/util/fncache.c
+++ b/tools/perf/util/fncache.c
@@ -8,7 +8,9 @@
#include "fncache.h"
#include "hashmap.h"
-static struct hashmap *fncache;
+DECLARE_HASHMAP(fncache_map, char *, bool);
+
+static struct fncache_map *fncache;
static size_t fncache__hash(long key, void *ctx __maybe_unused)
{
@@ -22,10 +24,10 @@ static bool fncache__equal(long key1, long key2, void *ctx __maybe_unused)
static void fncache__init(void)
{
- fncache = hashmap__new(fncache__hash, fncache__equal, /*ctx=*/NULL);
+ fncache = fncache_map__new(fncache__hash, fncache__equal, /*ctx=*/NULL);
}
-static struct hashmap *fncache__get(void)
+static struct fncache_map *fncache__get(void)
{
static pthread_once_t fncache_once = PTHREAD_ONCE_INIT;
@@ -36,23 +38,18 @@ static struct hashmap *fncache__get(void)
static bool lookup_fncache(const char *name, bool *res)
{
- struct hashmap *map = fncache__get();
- long val;
-
- if (!map || !hashmap__find(map, name, &val))
- return false;
+ struct fncache_map *map = fncache__get();
- *res = (val != 0);
- return true;
+ return map && hashmap__find(map, name, res);
}
static void update_fncache(const char *name, bool res)
{
- struct hashmap *map = fncache__get();
+ struct fncache_map *map = fncache__get();
char *old_key = NULL, *key = strdup(name);
if (map && key) {
- hashmap__set(map, key, res, &old_key, /*old_value*/NULL);
+ hashmap__set(map, key, res, &old_key, /*old_value=*/NULL);
free(old_key);
} else {
free(key);
--
2.56.1.703.gf848ed807c-goog
^ permalink raw reply related [flat|nested] 53+ messages in thread
* [PATCH v1 10/26] perf ftrace: Use generic hashmap for profile_hash
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
` (8 preceding siblings ...)
2026-11-13 21:48 ` [PATCH v1 09/26] perf fncache: Use generic hashmap for fncache Ian Rogers
@ 2026-11-13 21:48 ` 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
` (15 subsequent siblings)
25 siblings, 1 reply; 53+ messages in thread
From: Ian Rogers @ 2026-11-13 21:48 UTC (permalink / raw)
To: Peter Zijlstra, Ingo Molnar, Arnaldo Carvalho de Melo,
Namhyung Kim
Cc: Jiri Olsa, Ian Rogers, Adrian Hunter, James Clark,
linux-perf-users, linux-kernel
Convert perf_ftrace::profile_hash to DECLARE_HASHMAP(ftrace_profile_map,
char *, struct ftrace_profile_data *) and use the typed hashmap__* and
hashmap__for_each() wrappers.
Signed-off-by: Ian Rogers <irogers@google.com>
---
tools/perf/builtin-ftrace.c | 43 ++++++++++++++++++++++---------------
tools/perf/util/ftrace.h | 7 ++++--
2 files changed, 31 insertions(+), 19 deletions(-)
diff --git a/tools/perf/builtin-ftrace.c b/tools/perf/builtin-ftrace.c
index ac0107f5b891..02b4bf12451e 100644
--- a/tools/perf/builtin-ftrace.c
+++ b/tools/perf/builtin-ftrace.c
@@ -1228,7 +1228,7 @@ static int prepare_func_profile(struct perf_ftrace *ftrace)
ftrace->graph_tail = 1;
ftrace->graph_verbose = 0;
- ftrace->profile_hash = hashmap__new(profile_hash, profile_equal, NULL);
+ ftrace->profile_hash = ftrace_profile_map__new(profile_hash, profile_equal, NULL);
return ftrace->profile_hash ? 0 : -ENOMEM;
}
@@ -1347,17 +1347,22 @@ enum perf_ftrace_profile_sort_key {
static enum perf_ftrace_profile_sort_key profile_sort = PFP_SORT_TOTAL;
+struct ftrace_profile_entry {
+ char *name;
+ struct ftrace_profile_data *data;
+};
+
static int cmp_profile_data(const void *a, const void *b)
{
- const struct hashmap_entry *e1 = *(const struct hashmap_entry **)a;
- const struct hashmap_entry *e2 = *(const struct hashmap_entry **)b;
- struct ftrace_profile_data *p1 = e1->pvalue;
- struct ftrace_profile_data *p2 = e2->pvalue;
+ const struct ftrace_profile_entry *e1 = a;
+ const struct ftrace_profile_entry *e2 = b;
+ struct ftrace_profile_data *p1 = e1->data;
+ struct ftrace_profile_data *p2 = e2->data;
double v1, v2;
switch (profile_sort) {
case PFP_SORT_NAME:
- return strcmp(e1->pkey, e2->pkey);
+ return strcmp(e1->name, e2->name);
case PFP_SORT_AVG:
v1 = p1->st.mean;
v2 = p2->st.mean;
@@ -1386,8 +1391,10 @@ static int cmp_profile_data(const void *a, const void *b)
static void print_profile_result(struct perf_ftrace *ftrace)
{
- struct hashmap_entry *entry, **profile;
- size_t i, nr, bkt;
+ struct ftrace_profile_entry *profile;
+ size_t i, nr;
+ char *key;
+ struct ftrace_profile_data *val;
nr = hashmap__size(ftrace->profile_hash);
if (nr == 0)
@@ -1400,20 +1407,22 @@ static void print_profile_result(struct perf_ftrace *ftrace)
}
i = 0;
- hashmap__for_each_entry(ftrace->profile_hash, entry, bkt)
- profile[i++] = entry;
+ hashmap__for_each(ftrace->profile_hash, key, val) {
+ profile[i].name = key;
+ profile[i].data = val;
+ i++;
+ }
assert(i == nr);
- //cmp_profile_data(profile[0], profile[1]);
qsort(profile, nr, sizeof(*profile), cmp_profile_data);
printf("# %10s %10s %10s %10s %s\n",
"Total (us)", "Avg (us)", "Max (us)", "Count", "Function");
for (i = 0; i < nr; i++) {
- const char *name = profile[i]->pkey;
- struct ftrace_profile_data *p = profile[i]->pvalue;
+ const char *name = profile[i].name;
+ struct ftrace_profile_data *p = profile[i].data;
printf("%12.3f %10.3f %6"PRIu64".%03"PRIu64" %10.0f %s\n",
p->st.n * p->st.mean / 1000, p->st.mean / 1000,
@@ -1422,12 +1431,12 @@ static void print_profile_result(struct perf_ftrace *ftrace)
free(profile);
- hashmap__for_each_entry(ftrace->profile_hash, entry, bkt) {
- free((char *)entry->pkey);
- free(entry->pvalue);
+ hashmap__for_each(ftrace->profile_hash, key, val) {
+ free(key);
+ free(val);
}
- hashmap__free(ftrace->profile_hash);
+ ftrace_profile_map__free(ftrace->profile_hash);
ftrace->profile_hash = NULL;
}
diff --git a/tools/perf/util/ftrace.h b/tools/perf/util/ftrace.h
index 950f2efafad2..5ed52e66b8cb 100644
--- a/tools/perf/util/ftrace.h
+++ b/tools/perf/util/ftrace.h
@@ -3,12 +3,15 @@
#include <linux/list.h>
+#include "hashmap.h"
#include "target.h"
struct evlist;
-struct hashamp;
+struct ftrace_profile_data;
struct stats;
+DECLARE_HASHMAP(ftrace_profile_map, char *, struct ftrace_profile_data *);
+
struct perf_ftrace {
struct evlist *evlist;
struct target target;
@@ -18,7 +21,7 @@ struct perf_ftrace {
struct list_head graph_funcs;
struct list_head nograph_funcs;
struct list_head event_pair;
- struct hashmap *profile_hash;
+ struct ftrace_profile_map *profile_hash;
unsigned long percpu_buffer_size;
bool inherit;
bool use_nsec;
--
2.56.1.703.gf848ed807c-goog
^ permalink raw reply related [flat|nested] 53+ messages in thread
* [PATCH v1 11/26] perf trace: Use generic hashmap for syscall_stats
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
` (9 preceding siblings ...)
2026-11-13 21:48 ` [PATCH v1 10/26] perf ftrace: Use generic hashmap for profile_hash Ian Rogers
@ 2026-11-13 21:48 ` 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
` (14 subsequent siblings)
25 siblings, 1 reply; 53+ messages in thread
From: Ian Rogers @ 2026-11-13 21:48 UTC (permalink / raw)
To: Peter Zijlstra, Ingo Molnar, Arnaldo Carvalho de Melo,
Namhyung Kim
Cc: Jiri Olsa, Ian Rogers, Adrian Hunter, James Clark,
linux-perf-users, linux-kernel
Convert trace and thread_trace syscall_stats maps in builtin-trace.c to
DECLARE_HASHMAP(syscall_stats_map, long, struct syscall_stats *) and
replace direct struct hashmap_entry and map->sz accesses with
hashmap__for_each() and hashmap__size().
Signed-off-by: Ian Rogers <irogers@google.com>
---
tools/perf/builtin-trace.c | 43 ++++++++++++++++++++------------------
1 file changed, 23 insertions(+), 20 deletions(-)
diff --git a/tools/perf/builtin-trace.c b/tools/perf/builtin-trace.c
index 128efe1be8c3..dcd7890db8ac 100644
--- a/tools/perf/builtin-trace.c
+++ b/tools/perf/builtin-trace.c
@@ -141,6 +141,9 @@ struct syscall_fmt {
bool hexret;
};
+struct syscall_stats;
+DECLARE_HASHMAP(syscall_stats_map, long, struct syscall_stats *);
+
struct trace {
struct perf_env host_env;
struct perf_tool tool;
@@ -187,7 +190,7 @@ struct trace {
* into the key, but this would make the code inconsistent with the
* per-thread version.
*/
- struct hashmap *syscall_stats;
+ struct syscall_stats_map *syscall_stats;
double duration_filter;
double runtime_ms;
unsigned long pfmaj, pfmin;
@@ -1658,7 +1661,7 @@ struct thread_trace {
struct file *table;
} files;
- struct hashmap *syscall_stats;
+ struct syscall_stats_map *syscall_stats;
};
static size_t syscall_id_hash(long key, void *ctx __maybe_unused)
@@ -1671,22 +1674,22 @@ static bool syscall_id_equal(long key1, long key2, void *ctx __maybe_unused)
return key1 == key2;
}
-static struct hashmap *alloc_syscall_stats(void)
+static struct syscall_stats_map *alloc_syscall_stats(void)
{
- return hashmap__new(syscall_id_hash, syscall_id_equal, NULL);
+ return syscall_stats_map__new(syscall_id_hash, syscall_id_equal, NULL);
}
-static void delete_syscall_stats(struct hashmap *syscall_stats)
+static void delete_syscall_stats(struct syscall_stats_map *syscall_stats)
{
- struct hashmap_entry *pos;
- size_t bkt;
+ long key;
+ struct syscall_stats *val;
if (!syscall_stats)
return;
- hashmap__for_each_entry(syscall_stats, pos, bkt)
- zfree(&pos->pvalue);
- hashmap__free(syscall_stats);
+ hashmap__for_each(syscall_stats, key, val)
+ free(val);
+ syscall_stats_map__free(syscall_stats);
}
static struct thread_trace *thread_trace__new(struct trace *trace)
@@ -2865,7 +2868,7 @@ static void thread__update_stats(struct thread *thread, struct thread_trace *ttr
int id, struct perf_sample *sample, long err,
struct trace *trace)
{
- struct hashmap *syscall_stats = ttrace->syscall_stats;
+ struct syscall_stats_map *syscall_stats = ttrace->syscall_stats;
struct syscall_stats *stats = NULL;
u64 duration = 0;
@@ -5210,25 +5213,25 @@ static int entry_cmp(const void *e1, const void *e2)
return entry1->msecs > entry2->msecs ? -1 : 1;
}
-static struct syscall_entry *syscall__sort_stats(struct hashmap *syscall_stats)
+static struct syscall_entry *syscall__sort_stats(struct syscall_stats_map *syscall_stats)
{
struct syscall_entry *entry;
- struct hashmap_entry *pos;
- unsigned bkt, i, nr;
+ unsigned int i, nr;
+ long id;
+ struct syscall_stats *ss;
- nr = syscall_stats->sz;
+ nr = hashmap__size(syscall_stats);
entry = malloc(nr * sizeof(*entry));
if (entry == NULL)
return NULL;
i = 0;
- hashmap__for_each_entry(syscall_stats, pos, bkt) {
- struct syscall_stats *ss = pos->pvalue;
+ hashmap__for_each(syscall_stats, id, ss) {
struct stats *st = &ss->stats;
entry[i].stats = ss;
entry[i].msecs = (u64)st->n * (avg_stats(st) / NSEC_PER_MSEC);
- entry[i].syscall = pos->key;
+ entry[i].syscall = id;
i++;
}
assert(i == nr);
@@ -5238,7 +5241,7 @@ static struct syscall_entry *syscall__sort_stats(struct hashmap *syscall_stats)
}
static size_t syscall__dump_stats(struct trace *trace, int e_machine, FILE *fp,
- struct hashmap *syscall_stats)
+ struct syscall_stats_map *syscall_stats)
{
size_t printed = 0;
int lines = 0;
@@ -5255,7 +5258,7 @@ static size_t syscall__dump_stats(struct trace *trace, int e_machine, FILE *fp,
printed += fprintf(fp, " (msec) (msec) (msec) (msec) (%%)\n");
printed += fprintf(fp, " --------------- -------- ------ -------- --------- --------- --------- ------\n");
- for (size_t i = 0; i < syscall_stats->sz; i++) {
+ for (size_t i = 0; i < hashmap__size(syscall_stats); i++) {
struct syscall_entry *entry = &entries[i];
struct syscall_stats *stats = entry->stats;
--
2.56.1.703.gf848ed807c-goog
^ permalink raw reply related [flat|nested] 53+ messages in thread
* [PATCH v1 12/26] perf trace: Use generic hashmap for bpf-trace-summary
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
` (10 preceding siblings ...)
2026-11-13 21:48 ` [PATCH v1 11/26] perf trace: Use generic hashmap for syscall_stats Ian Rogers
@ 2026-11-13 21:48 ` 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
` (13 subsequent siblings)
25 siblings, 1 reply; 53+ messages in thread
From: Ian Rogers @ 2026-11-13 21:48 UTC (permalink / raw)
To: Peter Zijlstra, Ingo Molnar, Arnaldo Carvalho de Melo,
Namhyung Kim
Cc: Jiri Olsa, Ian Rogers, Adrian Hunter, James Clark,
linux-perf-users, linux-kernel
Convert the syscall summary hashmaps in util/bpf-trace-summary.c to
DECLARE_HASHMAP(syscall_data_map, long, struct syscall_data *) and use
hashmap__for_each() instead of struct hashmap_entry iteration.
Signed-off-by: Ian Rogers <irogers@google.com>
---
tools/perf/util/bpf-trace-summary.c | 18 ++++++++++--------
1 file changed, 10 insertions(+), 8 deletions(-)
diff --git a/tools/perf/util/bpf-trace-summary.c b/tools/perf/util/bpf-trace-summary.c
index 9a31dbf06cbb..2cc42f19d223 100644
--- a/tools/perf/util/bpf-trace-summary.c
+++ b/tools/perf/util/bpf-trace-summary.c
@@ -113,6 +113,8 @@ struct syscall_data {
struct syscall_node *nodes;
};
+DECLARE_HASHMAP(syscall_data_map, long, struct syscall_data *);
+
static int datacmp(const void *a, const void *b)
{
const struct syscall_data * const *sa = a;
@@ -169,7 +171,7 @@ static int print_common_stats(struct syscall_data *data, int max_summary, FILE *
return printed;
}
-static int update_thread_stats(struct hashmap *hash, struct syscall_key *map_key,
+static int update_thread_stats(struct syscall_data_map *hash, struct syscall_key *map_key,
struct syscall_stats *map_data)
{
struct syscall_data *data;
@@ -233,7 +235,7 @@ static int print_thread_stats(struct syscall_data **data, int nr_data, int max_s
return printed;
}
-static int update_total_stats(struct hashmap *hash, struct syscall_key *map_key,
+static int update_total_stats(struct syscall_data_map *hash, struct syscall_key *map_key,
struct syscall_stats *map_data)
{
struct syscall_data *data;
@@ -305,7 +307,7 @@ static int print_total_stats(struct syscall_data **data, int nr_data, int max_su
return printed;
}
-static int update_cgroup_stats(struct hashmap *hash, struct syscall_key *map_key,
+static int update_cgroup_stats(struct syscall_data_map *hash, struct syscall_key *map_key,
struct syscall_stats *map_data)
{
struct syscall_data *data;
@@ -379,12 +381,12 @@ int trace_print_bpf_summary(FILE *fp, int max_summary)
struct bpf_map *map = skel->maps.syscall_stats_map;
struct syscall_key *prev_key, key;
struct syscall_data **data = NULL;
- struct hashmap schash;
- struct hashmap_entry *entry;
+ struct syscall_data_map schash;
+ long entry_key;
+ struct syscall_data *entry_val;
int nr_data = 0;
int printed = 0;
int i;
- size_t bkt;
hashmap__init(&schash, sc_node_hash, sc_node_equal, /*ctx=*/NULL);
@@ -420,8 +422,8 @@ int trace_print_bpf_summary(FILE *fp, int max_summary)
goto out;
i = 0;
- hashmap__for_each_entry(&schash, entry, bkt)
- data[i++] = entry->pvalue;
+ hashmap__for_each(&schash, entry_key, entry_val)
+ data[i++] = entry_val;
qsort(data, nr_data, sizeof(*data), datacmp);
--
2.56.1.703.gf848ed807c-goog
^ permalink raw reply related [flat|nested] 53+ messages in thread
* [PATCH v1 13/26] perf lock: Use generic hashmap for slab_hash
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
` (11 preceding siblings ...)
2026-11-13 21:48 ` [PATCH v1 12/26] perf trace: Use generic hashmap for bpf-trace-summary Ian Rogers
@ 2026-11-13 21:48 ` 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
` (12 subsequent siblings)
25 siblings, 1 reply; 53+ messages in thread
From: Ian Rogers @ 2026-11-13 21:48 UTC (permalink / raw)
To: Peter Zijlstra, Ingo Molnar, Arnaldo Carvalho de Melo,
Namhyung Kim
Cc: Jiri Olsa, Ian Rogers, Adrian Hunter, James Clark,
linux-perf-users, linux-kernel
Convert slab_hash in util/bpf_lock_contention.c to
DECLARE_HASHMAP(slab_cache_map, u32, struct slab_cache_data *) and use
hashmap__for_each() during cleanup.
Signed-off-by: Ian Rogers <irogers@google.com>
---
tools/perf/util/bpf_lock_contention.c | 12 +++++++-----
1 file changed, 7 insertions(+), 5 deletions(-)
diff --git a/tools/perf/util/bpf_lock_contention.c b/tools/perf/util/bpf_lock_contention.c
index c20bd075664e..2bd8e62d3265 100644
--- a/tools/perf/util/bpf_lock_contention.c
+++ b/tools/perf/util/bpf_lock_contention.c
@@ -20,9 +20,11 @@
#include "bpf_skel/lock_contention.skel.h"
#include "bpf_skel/lock_data.h"
+DECLARE_HASHMAP(slab_cache_map, u32, struct slab_cache_data *);
+
static struct lock_contention_bpf *skel;
static bool has_slab_iter;
-static struct hashmap slab_hash;
+static struct slab_cache_map slab_hash;
static size_t slab_cache_hash(long key, void *ctx __maybe_unused)
{
@@ -99,11 +101,11 @@ static void run_slab_cache_iter(void)
static void exit_slab_cache_iter(void)
{
- struct hashmap_entry *cur;
- unsigned bkt;
+ u32 id;
+ struct slab_cache_data *val;
- hashmap__for_each_entry(&slab_hash, cur, bkt)
- free(cur->pvalue);
+ hashmap__for_each(&slab_hash, id, val)
+ free(val);
hashmap__clear(&slab_hash);
}
--
2.56.1.703.gf848ed807c-goog
^ permalink raw reply related [flat|nested] 53+ messages in thread
* [PATCH v1 14/26] perf stat: Use generic hashmap for per_pkg_mask
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
` (12 preceding siblings ...)
2026-11-13 21:48 ` [PATCH v1 13/26] perf lock: Use generic hashmap for slab_hash Ian Rogers
@ 2026-11-13 21:48 ` 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
` (11 subsequent siblings)
25 siblings, 1 reply; 53+ messages in thread
From: Ian Rogers @ 2026-11-13 21:48 UTC (permalink / raw)
To: Peter Zijlstra, Ingo Molnar, Arnaldo Carvalho de Melo,
Namhyung Kim
Cc: Jiri Olsa, Ian Rogers, Adrian Hunter, James Clark,
linux-perf-users, linux-kernel
Convert evsel::per_pkg_mask in util/evsel.{c,h} and util/stat.c to
DECLARE_HASHMAP(per_pkg_mask_map, uint64_t *, bool) and use
hashmap__for_each() to free keys in evsel__free_per_pkg().
Signed-off-by: Ian Rogers <irogers@google.com>
---
tools/perf/util/evsel.c | 10 +++++-----
tools/perf/util/evsel.h | 6 ++++--
tools/perf/util/stat.c | 6 +++---
3 files changed, 12 insertions(+), 10 deletions(-)
diff --git a/tools/perf/util/evsel.c b/tools/perf/util/evsel.c
index 6d69f63b7fb1..a37d0d66fb45 100644
--- a/tools/perf/util/evsel.c
+++ b/tools/perf/util/evsel.c
@@ -2094,7 +2094,7 @@ static void evsel__exit(struct evsel *evsel)
zfree(&evsel->unit);
zfree(&evsel->metric_id);
evsel__zero_per_pkg(evsel);
- hashmap__free(evsel->per_pkg_mask);
+ per_pkg_mask_map__free(evsel->per_pkg_mask);
evsel->per_pkg_mask = NULL;
if (evsel__priv_destructor)
evsel__priv_destructor(evsel->priv);
@@ -4761,12 +4761,12 @@ int evsel__store_ids(struct evsel *evsel, struct evlist *evlist)
void evsel__zero_per_pkg(struct evsel *evsel)
{
- struct hashmap_entry *cur;
- size_t bkt;
+ uint64_t *key;
+ bool val;
if (evsel->per_pkg_mask) {
- hashmap__for_each_entry(evsel->per_pkg_mask, cur, bkt)
- zfree(&cur->pkey);
+ hashmap__for_each(evsel->per_pkg_mask, key, val)
+ free(key);
hashmap__clear(evsel->per_pkg_mask);
}
diff --git a/tools/perf/util/evsel.h b/tools/perf/util/evsel.h
index 53f3641b66e1..eeb2ece1490c 100644
--- a/tools/perf/util/evsel.h
+++ b/tools/perf/util/evsel.h
@@ -13,6 +13,7 @@
#include <internal/evsel.h>
#include <perf/evsel.h>
+#include "hashmap.h"
#include "symbol_conf.h"
struct bperf_follower_bpf;
@@ -20,7 +21,6 @@ struct bperf_leader_bpf;
struct bpf_counter_ops;
struct bpf_object;
struct cgroup;
-struct hashmap;
struct perf_counts;
struct perf_pmu;
struct perf_stat_config;
@@ -28,6 +28,8 @@ struct perf_stat_evsel;
struct target;
union perf_event;
+DECLARE_HASHMAP(per_pkg_mask_map, uint64_t *, bool);
+
typedef int (evsel__sb_cb_t)(union perf_event *event, void *data);
/** struct evsel - event selector
@@ -135,7 +137,7 @@ struct evsel {
bool fallenback_eacces;
bool fallenback_eopnotsupp;
u8 probe_type:3;
- struct hashmap *per_pkg_mask;
+ struct per_pkg_mask_map *per_pkg_mask;
int err;
int script_output_type;
struct {
diff --git a/tools/perf/util/stat.c b/tools/perf/util/stat.c
index 097985da3dbb..c502f4928f9d 100644
--- a/tools/perf/util/stat.c
+++ b/tools/perf/util/stat.c
@@ -310,7 +310,7 @@ static bool pkg_id_equal(long __key1, long __key2, void *ctx __maybe_unused)
static int check_per_pkg(struct evsel *counter, struct perf_counts_values *vals,
int cpu_map_idx, bool *skip)
{
- struct hashmap *mask = counter->per_pkg_mask;
+ struct per_pkg_mask_map *mask = counter->per_pkg_mask;
struct perf_cpu_map *cpus = evsel__cpus(counter);
struct perf_cpu cpu = perf_cpu_map__cpu(cpus, cpu_map_idx);
int s, d, ret = 0;
@@ -325,7 +325,7 @@ static int check_per_pkg(struct evsel *counter, struct perf_counts_values *vals,
return 0;
if (!mask) {
- mask = hashmap__new(pkg_id_hash, pkg_id_equal, NULL);
+ mask = per_pkg_mask_map__new(pkg_id_hash, pkg_id_equal, NULL);
if (!mask)
return -ENOMEM;
@@ -364,7 +364,7 @@ static int check_per_pkg(struct evsel *counter, struct perf_counts_values *vals,
*skip = true;
free(key);
} else {
- ret = hashmap__add(mask, key, 1);
+ ret = hashmap__add(mask, key, true);
if (ret)
free(key);
}
--
2.56.1.703.gf848ed807c-goog
^ permalink raw reply related [flat|nested] 53+ messages in thread
* [PATCH v1 15/26] perf expr: Use generic hashmap for expr_ids
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
` (13 preceding siblings ...)
2026-11-13 21:48 ` [PATCH v1 14/26] perf stat: Use generic hashmap for per_pkg_mask Ian Rogers
@ 2026-11-13 21:48 ` 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
` (10 subsequent siblings)
25 siblings, 1 reply; 53+ messages in thread
From: Ian Rogers @ 2026-11-13 21:48 UTC (permalink / raw)
To: Peter Zijlstra, Ingo Molnar, Arnaldo Carvalho de Melo,
Namhyung Kim
Cc: Jiri Olsa, Ian Rogers, Adrian Hunter, James Clark,
linux-perf-users, linux-kernel
Convert expr_parse_ctx::ids and ids__* helpers across util/expr.{c,h,y},
util/metricgroup.c, tests/expr.c, and tests/pmu-events.c to
DECLARE_HASHMAP(expr_ids_map, const char *, struct expr_id_data *).
Signed-off-by: Ian Rogers <irogers@google.com>
---
tools/perf/tests/expr.c | 2 +-
tools/perf/tests/pmu-events.c | 16 +++---
tools/perf/util/expr.c | 99 +++++++++++++++++------------------
tools/perf/util/expr.h | 18 ++++---
tools/perf/util/expr.y | 2 +-
tools/perf/util/metricgroup.c | 28 +++++-----
6 files changed, 83 insertions(+), 82 deletions(-)
diff --git a/tools/perf/tests/expr.c b/tools/perf/tests/expr.c
index 726cf8d4da28..a2c7dc4d71c9 100644
--- a/tools/perf/tests/expr.c
+++ b/tools/perf/tests/expr.c
@@ -15,7 +15,7 @@
static int test_ids_union(void)
{
- struct hashmap *ids1, *ids2;
+ struct expr_ids_map *ids1, *ids2;
/* Empty union. */
ids1 = ids__new();
diff --git a/tools/perf/tests/pmu-events.c b/tools/perf/tests/pmu-events.c
index e6e4f4cf1fa2..42862501f0bf 100644
--- a/tools/perf/tests/pmu-events.c
+++ b/tools/perf/tests/pmu-events.c
@@ -964,10 +964,10 @@ static struct test_metric metrics[] = {
static int metric_parse_fake(const char *metric_name, const char *str)
{
struct expr_parse_ctx *ctx;
- struct hashmap_entry *cur;
+ const char *key;
+ struct expr_id_data *val;
double result;
int ret = -1;
- size_t bkt;
int i;
pr_debug("parsing '%s': '%s'\n", metric_name, str);
@@ -989,11 +989,11 @@ static int metric_parse_fake(const char *metric_name, const char *str)
* make them unique.
*/
i = 1;
- hashmap__for_each_entry(ctx->ids, cur, bkt)
- expr__add_id_val(ctx, strdup(cur->pkey), i++);
+ hashmap__for_each(ctx->ids, key, val)
+ expr__add_id_val(ctx, strdup(key), i++);
- hashmap__for_each_entry(ctx->ids, cur, bkt) {
- if (check_parse_fake(cur->pkey)) {
+ hashmap__for_each(ctx->ids, key, val) {
+ if (check_parse_fake(key)) {
pr_err("check_parse_fake failed\n");
goto out;
}
@@ -1006,8 +1006,8 @@ static int metric_parse_fake(const char *metric_name, const char *str)
* resolve divide by zero issues.
*/
i = 1024;
- hashmap__for_each_entry(ctx->ids, cur, bkt)
- expr__add_id_val(ctx, strdup(cur->pkey), i--);
+ hashmap__for_each(ctx->ids, key, val)
+ expr__add_id_val(ctx, strdup(key), i--);
if (expr__parse(&result, ctx, str)) {
pr_err("expr__parse failed for %s\n", metric_name);
/* The following have hard to avoid divide by zero. */
diff --git a/tools/perf/util/expr.c b/tools/perf/util/expr.c
index ac0243576def..d548ae5a4ec9 100644
--- a/tools/perf/util/expr.c
+++ b/tools/perf/util/expr.c
@@ -64,48 +64,46 @@ static bool key_equal(long key1, long key2, void *ctx __maybe_unused)
return !strcmp((const char *)key1, (const char *)key2);
}
-struct hashmap *ids__new(void)
+struct expr_ids_map *ids__new(void)
{
- return hashmap__new(key_hash, key_equal, NULL);
+ return expr_ids_map__new(key_hash, key_equal, NULL);
}
-void ids__free(struct hashmap *ids)
+void ids__free(struct expr_ids_map *ids)
{
- struct hashmap_entry *cur;
- size_t bkt;
+ const char *key;
+ struct expr_id_data *val;
if (ids == NULL)
return;
- hashmap__for_each_entry(ids, cur, bkt) {
- zfree(&cur->pkey);
- zfree(&cur->pvalue);
+ hashmap__for_each(ids, key, val) {
+ free((char *)key);
+ free(val);
}
- hashmap__free(ids);
+ expr_ids_map__free(ids);
}
-int ids__insert(struct hashmap *ids, const char *id)
+int ids__insert(struct expr_ids_map *ids, const char *id)
{
struct expr_id_data *data_ptr = NULL, *old_data = NULL;
- char *old_key = NULL;
+ const char *old_key = NULL;
int ret;
ret = hashmap__set(ids, id, data_ptr, &old_key, &old_data);
if (ret)
free(data_ptr);
- free(old_key);
+ free((char *)old_key);
free(old_data);
return ret;
}
-struct hashmap *ids__union(struct hashmap *ids1, struct hashmap *ids2)
+struct expr_ids_map *ids__union(struct expr_ids_map *ids1, struct expr_ids_map *ids2)
{
- size_t bkt;
- struct hashmap_entry *cur;
+ const char *key, *old_key = NULL;
+ struct expr_id_data *val, *old_data = NULL;
int ret;
- struct expr_id_data *old_data = NULL;
- char *old_key = NULL;
if (!ids1)
return ids2;
@@ -113,24 +111,24 @@ struct hashmap *ids__union(struct hashmap *ids1, struct hashmap *ids2)
if (!ids2)
return ids1;
- if (hashmap__size(ids1) < hashmap__size(ids2)) {
- struct hashmap *tmp = ids1;
+ if (hashmap__size(ids1) < hashmap__size(ids2)) {
+ struct expr_ids_map *tmp = ids1;
ids1 = ids2;
ids2 = tmp;
}
- hashmap__for_each_entry(ids2, cur, bkt) {
- ret = hashmap__set(ids1, cur->key, cur->value, &old_key, &old_data);
- free(old_key);
+ hashmap__for_each(ids2, key, val) {
+ ret = hashmap__set(ids1, key, val, &old_key, &old_data);
+ free((char *)old_key);
free(old_data);
if (ret) {
- hashmap__free(ids1);
- hashmap__free(ids2);
+ expr_ids_map__free(ids1);
+ expr_ids_map__free(ids2);
return NULL;
}
}
- hashmap__free(ids2);
+ expr_ids_map__free(ids2);
return ids1;
}
@@ -151,7 +149,7 @@ int expr__add_id_val_source_count_aggr_nr(struct expr_parse_ctx *ctx, const char
double val, int source_count, int aggr_nr)
{
struct expr_id_data *data_ptr = NULL, *old_data = NULL;
- char *old_key = NULL;
+ const char *old_key = NULL;
int ret;
data_ptr = malloc(sizeof(*data_ptr));
@@ -170,7 +168,7 @@ int expr__add_id_val_source_count_aggr_nr(struct expr_parse_ctx *ctx, const char
data_ptr->val.source_count += old_data->val.source_count;
data_ptr->val.aggr_nr += old_data->val.aggr_nr;
}
- free(old_key);
+ free((char *)old_key);
free(old_data);
return ret;
}
@@ -185,7 +183,7 @@ int expr__add_id_val_source_count(struct expr_parse_ctx *ctx, const char *id,
int expr__add_ref(struct expr_parse_ctx *ctx, struct metric_ref *ref)
{
struct expr_id_data *data_ptr = NULL, *old_data = NULL;
- char *old_key = NULL;
+ const char *old_key = NULL;
char *name;
int ret;
@@ -210,13 +208,15 @@ int expr__add_ref(struct expr_parse_ctx *ctx, struct metric_ref *ref)
data_ptr->kind = EXPR_ID_DATA__REF;
ret = hashmap__set(ctx->ids, name, data_ptr, &old_key, &old_data);
- if (ret)
+ if (ret) {
free(data_ptr);
+ free(name);
+ }
pr_debug2("adding ref metric %s: %s\n",
ref->metric_name, ref->metric_expr);
- free(old_key);
+ free((char *)old_key);
free(old_data);
return ret;
}
@@ -232,12 +232,11 @@ int expr__get_id(struct expr_parse_ctx *ctx, const char *id,
bool expr__subset_of_ids(struct expr_parse_ctx *haystack,
struct expr_parse_ctx *needles)
{
- struct hashmap_entry *cur;
- size_t bkt;
- struct expr_id_data *data;
+ const char *key;
+ struct expr_id_data *val, *data;
- hashmap__for_each_entry(needles->ids, cur, bkt) {
- if (expr__get_id(haystack, cur->pkey, &data))
+ hashmap__for_each(needles->ids, key, val) {
+ if (expr__get_id(haystack, key, &data))
return false;
}
return true;
@@ -285,10 +284,10 @@ int expr__resolve_id(struct expr_parse_ctx *ctx, const char *id,
void expr__del_id(struct expr_parse_ctx *ctx, const char *id)
{
struct expr_id_data *old_val = NULL;
- char *old_key = NULL;
+ const char *old_key = NULL;
hashmap__delete(ctx->ids, id, &old_key, &old_val);
- free(old_key);
+ free((char *)old_key);
free(old_val);
}
@@ -300,7 +299,7 @@ struct expr_parse_ctx *expr__ctx_new(void)
if (!ctx)
return NULL;
- ctx->ids = hashmap__new(key_hash, key_equal, NULL);
+ ctx->ids = expr_ids_map__new(key_hash, key_equal, NULL);
if (!ctx->ids) {
free(ctx);
return NULL;
@@ -311,30 +310,30 @@ struct expr_parse_ctx *expr__ctx_new(void)
void expr__ctx_clear(struct expr_parse_ctx *ctx)
{
- struct hashmap_entry *cur;
- size_t bkt;
+ const char *key;
+ struct expr_id_data *val;
- hashmap__for_each_entry(ctx->ids, cur, bkt) {
- zfree(&cur->pkey);
- zfree(&cur->pvalue);
+ hashmap__for_each(ctx->ids, key, val) {
+ free((char *)key);
+ free(val);
}
hashmap__clear(ctx->ids);
}
void expr__ctx_free(struct expr_parse_ctx *ctx)
{
- struct hashmap_entry *cur;
- size_t bkt;
+ const char *key;
+ struct expr_id_data *val;
if (!ctx)
return;
zfree(&ctx->sctx.user_requested_cpu_list);
- hashmap__for_each_entry(ctx->ids, cur, bkt) {
- zfree(&cur->pkey);
- zfree(&cur->pvalue);
+ hashmap__for_each(ctx->ids, key, val) {
+ free((char *)key);
+ free(val);
}
- hashmap__free(ctx->ids);
+ expr_ids_map__free(ctx->ids);
free(ctx);
}
@@ -438,7 +437,7 @@ double expr__has_event(const struct expr_parse_ctx *ctx, bool compute_ids, const
struct evlist *tmp;
double ret;
- if (hashmap__find(ctx->ids, id, /*value=*/NULL))
+ if (hashmap__find(ctx->ids, id, /*value_ptr=*/NULL))
return 1.0;
if (!compute_ids)
diff --git a/tools/perf/util/expr.h b/tools/perf/util/expr.h
index ed12e4007d2d..57edcb3f70ea 100644
--- a/tools/perf/util/expr.h
+++ b/tools/perf/util/expr.h
@@ -2,9 +2,13 @@
#ifndef PARSE_CTX_H
#define PARSE_CTX_H 1
-struct hashmap;
+#include "hashmap.h"
+
+struct expr_id_data;
struct metric_ref;
+DECLARE_HASHMAP(expr_ids_map, const char *, struct expr_id_data *);
+
struct expr_scanner_ctx {
char *user_requested_cpu_list;
int runtime;
@@ -13,20 +17,18 @@ struct expr_scanner_ctx {
};
struct expr_parse_ctx {
- struct hashmap *ids;
+ struct expr_ids_map *ids;
struct expr_scanner_ctx sctx;
};
-struct expr_id_data;
-
-struct hashmap *ids__new(void);
-void ids__free(struct hashmap *ids);
-int ids__insert(struct hashmap *ids, const char *id);
+struct expr_ids_map *ids__new(void);
+void ids__free(struct expr_ids_map *ids);
+int ids__insert(struct expr_ids_map *ids, const char *id);
/*
* Union two sets of ids (hashmaps) and construct a third, freeing ids1 and
* ids2.
*/
-struct hashmap *ids__union(struct hashmap *ids1, struct hashmap *ids2);
+struct expr_ids_map *ids__union(struct expr_ids_map *ids1, struct expr_ids_map *ids2);
struct expr_parse_ctx *expr__ctx_new(void);
void expr__ctx_clear(struct expr_parse_ctx *ctx);
diff --git a/tools/perf/util/expr.y b/tools/perf/util/expr.y
index e20f649354cf..d39ba27d75bc 100644
--- a/tools/perf/util/expr.y
+++ b/tools/perf/util/expr.y
@@ -29,7 +29,7 @@ int expr_lex(YYSTYPE * yylval_param , void *yyscanner);
* When creating ids, holds the working set of event ids. NULL
* implies the set is empty.
*/
- struct hashmap *ids;
+ struct expr_ids_map *ids;
/*
* The metric value. When not creating ids this is the value
* read from a counter, a constant or some computed value. When
diff --git a/tools/perf/util/metricgroup.c b/tools/perf/util/metricgroup.c
index 779fe56fe8d0..41887fad4968 100644
--- a/tools/perf/util/metricgroup.c
+++ b/tools/perf/util/metricgroup.c
@@ -289,7 +289,7 @@ static bool contains_metric_id(struct evsel **metric_events, int num_events,
* @metric_evlist: the list of perf events.
* @out_metric_events: holds the created metric events array.
*/
-static int setup_metric_events(const char *pmu, struct hashmap *ids,
+static int setup_metric_events(const char *pmu, struct expr_ids_map *ids,
struct evlist *metric_evlist,
struct evsel ***out_metric_events)
{
@@ -563,16 +563,16 @@ static int metricgroup__build_event_string(struct strbuf *events,
const char *modifier,
bool group_events)
{
- struct hashmap_entry *cur;
- size_t bkt;
+ const char *id;
+ struct expr_id_data *val;
bool no_group = true, has_tool_events = false;
bool tool_events[TOOL_PMU__EVENT_MAX] = {false};
int ret = 0;
#define RETURN_IF_NON_ZERO(x) do { if (x) return x; } while (0)
- hashmap__for_each_entry(ctx->ids, cur, bkt) {
- const char *sep, *rsep, *id = cur->pkey;
+ hashmap__for_each(ctx->ids, id, val) {
+ const char *sep, *rsep;
enum tool_pmu_event ev;
pr_debug("found event %s\n", id);
@@ -748,8 +748,8 @@ static int resolve_metric(struct list_head *metric_list,
const struct visited_metric *visited,
const struct pmu_metrics_table *table)
{
- struct hashmap_entry *cur;
- size_t bkt;
+ const char *key;
+ struct expr_id_data *val;
struct to_resolve {
/* The metric to resolve. */
struct pmu_metric pm;
@@ -765,10 +765,10 @@ static int resolve_metric(struct list_head *metric_list,
* Iterate all the parsed IDs and if there's a matching metric and it to
* the pending array.
*/
- hashmap__for_each_entry(root_metric->pctx->ids, cur, bkt) {
+ hashmap__for_each(root_metric->pctx->ids, key, val) {
struct pmu_metric pm;
- if (pmu_metrics_table__find_metric(table, pmu, cur->pkey,
+ if (pmu_metrics_table__find_metric(table, pmu, key,
metricgroup__find_metric_callback,
&pm) != PMU_METRICS__NOT_FOUND) {
pending = realloc(pending,
@@ -777,7 +777,7 @@ static int resolve_metric(struct list_head *metric_list,
return -ENOMEM;
memcpy(&pending[pending_cnt].pm, &pm, sizeof(pm));
- pending[pending_cnt].key = cur->pkey;
+ pending[pending_cnt].key = key;
pending_cnt++;
}
}
@@ -1229,8 +1229,8 @@ static void find_tool_events(const struct list_head *metric_list,
static int build_combined_expr_ctx(const struct list_head *metric_list,
struct expr_parse_ctx **combined)
{
- struct hashmap_entry *cur;
- size_t bkt;
+ const char *key;
+ struct expr_id_data *val;
struct metric *m;
char *dup;
int ret;
@@ -1241,8 +1241,8 @@ static int build_combined_expr_ctx(const struct list_head *metric_list,
list_for_each_entry(m, metric_list, nd) {
if (!m->group_events && !m->modifier) {
- hashmap__for_each_entry(m->pctx->ids, cur, bkt) {
- dup = strdup(cur->pkey);
+ hashmap__for_each(m->pctx->ids, key, val) {
+ dup = strdup(key);
if (!dup) {
ret = -ENOMEM;
goto err_out;
--
2.56.1.703.gf848ed807c-goog
^ permalink raw reply related [flat|nested] 53+ messages in thread
* [PATCH v1 16/26] perf pmu: Use generic hashmap for pmu aliases
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
` (14 preceding siblings ...)
2026-11-13 21:48 ` [PATCH v1 15/26] perf expr: Use generic hashmap for expr_ids Ian Rogers
@ 2026-11-13 21:48 ` 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
` (9 subsequent siblings)
25 siblings, 1 reply; 53+ messages in thread
From: Ian Rogers @ 2026-11-13 21:48 UTC (permalink / raw)
To: Peter Zijlstra, Ingo Molnar, Arnaldo Carvalho de Melo,
Namhyung Kim
Cc: Jiri Olsa, Ian Rogers, Adrian Hunter, James Clark,
linux-perf-users, linux-kernel
Convert perf_pmu::aliases in util/pmu.{c,h} to
DECLARE_HASHMAP(pmu_aliases_map, const char *, struct perf_pmu_alias *)
and replace hashmap__for_each_entry() loops with hashmap__for_each().
Signed-off-by: Ian Rogers <irogers@google.com>
---
tools/perf/util/pmu.c | 30 ++++++++++++++----------------
tools/perf/util/pmu.h | 7 +++++--
2 files changed, 19 insertions(+), 18 deletions(-)
diff --git a/tools/perf/util/pmu.c b/tools/perf/util/pmu.c
index a63fa0d9cfa5..cabc14260719 100644
--- a/tools/perf/util/pmu.c
+++ b/tools/perf/util/pmu.c
@@ -425,16 +425,16 @@ static void perf_pmu_free_alias(struct perf_pmu_alias *alias)
static void perf_pmu__del_aliases(struct perf_pmu *pmu)
{
- struct hashmap_entry *entry;
- size_t bkt;
+ const char *key;
+ struct perf_pmu_alias *val;
if (!pmu->aliases)
return;
- hashmap__for_each_entry(pmu->aliases, entry, bkt)
- perf_pmu_free_alias(entry->pvalue);
+ hashmap__for_each(pmu->aliases, key, val)
+ perf_pmu_free_alias(val);
- hashmap__free(pmu->aliases);
+ pmu_aliases_map__free(pmu->aliases);
pmu->aliases = NULL;
}
@@ -676,7 +676,7 @@ static int perf_pmu__new_alias(struct perf_pmu *pmu, const char *name,
break;
}
- hashmap__set(pmu->aliases, alias->name, alias, /*old_key=*/ NULL, &old_alias);
+ hashmap__set(pmu->aliases, alias->name, alias, /*old_key=*/NULL, &old_alias);
perf_pmu_free_alias(old_alias);
return 0;
}
@@ -1198,7 +1198,7 @@ int perf_pmu__init(struct perf_pmu *pmu, __u32 type, const char *name)
if (!pmu->name)
return -ENOMEM;
- pmu->aliases = hashmap__new(aliases__hash, aliases__equal, /*ctx=*/ NULL);
+ pmu->aliases = pmu_aliases_map__new(aliases__hash, aliases__equal, /*ctx=*/NULL);
if (!pmu->aliases)
return -ENOMEM;
@@ -1313,7 +1313,7 @@ struct perf_pmu *perf_pmu__create_placeholder_core_pmu(struct list_head *core_pm
pmu->cpus = cpu_map__online();
INIT_LIST_HEAD(&pmu->format);
- pmu->aliases = hashmap__new(aliases__hash, aliases__equal, /*ctx=*/ NULL);
+ pmu->aliases = pmu_aliases_map__new(aliases__hash, aliases__equal, /*ctx=*/NULL);
if (!pmu->aliases) {
perf_cpu_map__put(pmu->cpus);
free((char *)pmu->name);
@@ -2276,8 +2276,8 @@ int perf_pmu__for_each_event(struct perf_pmu *pmu, bool skip_duplicate_pmus,
.event_type_desc = "Kernel PMU event",
};
int ret = 0;
- struct hashmap_entry *entry;
- size_t bkt;
+ const char *key;
+ struct perf_pmu_alias *event;
size_t size_rem;
if (perf_pmu__is_tracepoint(pmu))
@@ -2289,8 +2289,7 @@ int perf_pmu__for_each_event(struct perf_pmu *pmu, bool skip_duplicate_pmus,
pmu_aliases_parse(pmu);
pmu_add_cpu_aliases(pmu);
- hashmap__for_each_entry(pmu->aliases, entry, bkt) {
- struct perf_pmu_alias *event = entry->pvalue;
+ hashmap__for_each(pmu->aliases, key, event) {
size_t buf_used, pmu_name_len;
if (perf_pmu__is_tool(pmu) && tool_pmu__skip_event(event->name))
@@ -2809,16 +2808,15 @@ void perf_pmu__delete(struct perf_pmu *pmu)
const char *perf_pmu__name_from_config(struct perf_pmu *pmu, u64 config)
{
- struct hashmap_entry *entry;
- size_t bkt;
+ const char *key;
+ struct perf_pmu_alias *event;
if (!pmu)
return NULL;
pmu_aliases_parse(pmu);
pmu_add_cpu_aliases(pmu);
- hashmap__for_each_entry(pmu->aliases, entry, bkt) {
- struct perf_pmu_alias *event = entry->pvalue;
+ hashmap__for_each(pmu->aliases, key, event) {
struct perf_event_attr attr = {.config = 0,};
int ret = perf_pmu__parse_terms_to_attr(pmu, event->terms, &attr);
diff --git a/tools/perf/util/pmu.h b/tools/perf/util/pmu.h
index 2c8916ad073e..928aee2486e7 100644
--- a/tools/perf/util/pmu.h
+++ b/tools/perf/util/pmu.h
@@ -10,15 +10,18 @@
#include <stdio.h>
#include "parse-events.h"
#include "pmu-events/pmu-events.h"
+#include "hashmap.h"
#include "map_symbol.h"
#include "mem-events.h"
struct evsel_config_term;
-struct hashmap;
struct perf_cpu_map;
struct perf_env;
+struct perf_pmu_alias;
struct print_callbacks;
+DECLARE_HASHMAP(pmu_aliases_map, const char *, struct perf_pmu_alias *);
+
enum {
PERF_PMU_FORMAT_VALUE_CONFIG,
PERF_PMU_FORMAT_VALUE_CONFIG1,
@@ -143,7 +146,7 @@ struct perf_pmu {
* event read from <sysfs>/bus/event_source/devices/<name>/events/ or
* from json events in pmu-events.c.
*/
- struct hashmap *aliases;
+ struct pmu_aliases_map *aliases;
/**
* @events_table: The events table for json events in pmu-events.c.
*/
--
2.56.1.703.gf848ed807c-goog
^ permalink raw reply related [flat|nested] 53+ messages in thread
* [PATCH v1 17/26] perf hwmon_pmu: Use generic hashmap for hwmon events
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
` (15 preceding siblings ...)
2026-11-13 21:48 ` [PATCH v1 16/26] perf pmu: Use generic hashmap for pmu aliases Ian Rogers
@ 2026-11-13 21:48 ` 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
` (8 subsequent siblings)
25 siblings, 1 reply; 53+ messages in thread
From: Ian Rogers @ 2026-11-13 21:48 UTC (permalink / raw)
To: Peter Zijlstra, Ingo Molnar, Arnaldo Carvalho de Melo,
Namhyung Kim
Cc: Jiri Olsa, Ian Rogers, Adrian Hunter, James Clark,
linux-perf-users, linux-kernel
Convert hwmon_pmu::events in util/hwmon_pmu.c to
DECLARE_HASHMAP(hwmon_pmu_events_map, long, struct hwmon_pmu_event_value *)
and use hashmap__for_each{,_safe}().
Signed-off-by: Ian Rogers <irogers@google.com>
---
tools/perf/util/hwmon_pmu.c | 57 +++++++++++++++++--------------------
1 file changed, 26 insertions(+), 31 deletions(-)
diff --git a/tools/perf/util/hwmon_pmu.c b/tools/perf/util/hwmon_pmu.c
index c150fc0d7bd3..95049f5fead3 100644
--- a/tools/perf/util/hwmon_pmu.c
+++ b/tools/perf/util/hwmon_pmu.c
@@ -101,9 +101,12 @@ static const char *const hwmon_units[HWMON_TYPE_MAX] = {
"'C", /* temp */
};
+struct hwmon_pmu_event_value;
+DECLARE_HASHMAP(hwmon_pmu_events_map, long, struct hwmon_pmu_event_value *);
+
struct hwmon_pmu {
struct perf_pmu pmu;
- struct hashmap events;
+ struct hwmon_pmu_events_map events;
char *hwmon_dir;
};
@@ -237,8 +240,8 @@ static void fix_name(char *p)
static int hwmon_pmu__read_events(struct hwmon_pmu *pmu)
{
int err = 0;
- struct hashmap_entry *cur, *tmp;
- size_t bkt;
+ long key_val;
+ struct hwmon_pmu_event_value *value;
struct io_dirent64 *ent;
struct io_dir dir;
@@ -257,7 +260,6 @@ static int hwmon_pmu__read_events(struct hwmon_pmu *pmu)
enum hwmon_item item;
bool alarm;
union hwmon_pmu_event_key key = { .type_and_num = 0 };
- struct hwmon_pmu_event_value *value;
if (ent->d_type != DT_REG)
continue;
@@ -324,16 +326,15 @@ static int hwmon_pmu__read_events(struct hwmon_pmu *pmu)
if (hashmap__size(&pmu->events) == 0)
pr_debug2("hwmon_pmu: %s has no events\n", pmu->pmu.name);
- hashmap__for_each_entry_safe((&pmu->events), cur, tmp, bkt) {
+ hashmap__for_each_safe(&pmu->events, key_val, value) {
union hwmon_pmu_event_key key = {
- .type_and_num = cur->key,
+ .type_and_num = key_val,
};
- struct hwmon_pmu_event_value *value = cur->pvalue;
if (!test_bit(HWMON_ITEM_INPUT, value->items)) {
pr_debug("hwmon_pmu: %s removing event '%s%d' that has no input file\n",
pmu->pmu.name, hwmon_type_strs[key.type], key.num);
- hashmap__delete(&pmu->events, key.type_and_num, &key, &value);
+ hashmap__delete(&pmu->events, key_val, NULL, NULL);
zfree(&value->label);
zfree(&value->name);
free(value);
@@ -397,12 +398,10 @@ struct perf_pmu *hwmon_pmu__new(struct list_head *pmus, const char *hwmon_dir,
void hwmon_pmu__exit(struct perf_pmu *pmu)
{
struct hwmon_pmu *hwm = container_of(pmu, struct hwmon_pmu, pmu);
- struct hashmap_entry *cur, *tmp;
- size_t bkt;
-
- hashmap__for_each_entry_safe((&hwm->events), cur, tmp, bkt) {
- struct hwmon_pmu_event_value *value = cur->pvalue;
+ long key;
+ struct hwmon_pmu_event_value *value;
+ hashmap__for_each_safe(&hwm->events, key, value) {
zfree(&value->label);
zfree(&value->name);
free(value);
@@ -463,13 +462,13 @@ static size_t hwmon_pmu__describe_items(struct hwmon_pmu *hwm, char *out_buf, si
int hwmon_pmu__for_each_event(struct perf_pmu *pmu, void *state, pmu_event_callback cb)
{
struct hwmon_pmu *hwm = container_of(pmu, struct hwmon_pmu, pmu);
- struct hashmap_entry *cur;
- size_t bkt;
+ long key_val;
+ struct hwmon_pmu_event_value *value;
if (hwmon_pmu__read_events(hwm))
return false;
- hashmap__for_each_entry((&hwm->events), cur, bkt) {
+ hashmap__for_each(&hwm->events, key_val, value) {
static const char *const hwmon_scale_units[HWMON_TYPE_MAX] = {
NULL,
"0.001V", /* cpu */
@@ -500,9 +499,8 @@ int hwmon_pmu__for_each_event(struct perf_pmu *pmu, void *state, pmu_event_callb
char desc_buf[256];
char encoding_buf[128];
union hwmon_pmu_event_key key = {
- .type_and_num = cur->key,
+ .type_and_num = key_val,
};
- struct hwmon_pmu_event_value *value = cur->pvalue;
struct pmu_event_info info = {
.pmu = pmu,
.name = value->name,
@@ -537,7 +535,7 @@ int hwmon_pmu__for_each_event(struct perf_pmu *pmu, void *state, pmu_event_callb
key, value->alarm_items, /*is_alarm=*/true);
snprintf(encoding_buf, sizeof(encoding_buf), "%s/config=0x%lx/",
- pmu->name, cur->key);
+ pmu->name, key_val);
ret = cb(state, &info);
if (ret)
@@ -560,8 +558,8 @@ bool hwmon_pmu__have_event(struct perf_pmu *pmu, const char *name)
enum hwmon_type type;
int number;
union hwmon_pmu_event_key key = { .type_and_num = 0 };
- struct hashmap_entry *cur;
- size_t bkt;
+ long key_val;
+ struct hwmon_pmu_event_value *value;
if (!parse_hwmon_filename(name, &type, &number, /*item=*/NULL, /*is_alarm=*/NULL))
return false;
@@ -571,15 +569,13 @@ bool hwmon_pmu__have_event(struct perf_pmu *pmu, const char *name)
key.type = type;
key.num = number;
- if (hashmap__find(&hwm->events, key.type_and_num, /*value=*/NULL))
+ if (hashmap__find(&hwm->events, key.type_and_num, /*value_ptr=*/NULL))
return true;
if (key.num != -1)
return false;
/* Item is of form <type>_ which means we should match <type>_<label>. */
- hashmap__for_each_entry((&hwm->events), cur, bkt) {
- struct hwmon_pmu_event_value *value = cur->pvalue;
-
- key.type_and_num = cur->key;
+ hashmap__for_each(&hwm->events, key_val, value) {
+ key.type_and_num = key_val;
if (key.type == type && value->name && !strcasecmp(name, value->name))
return true;
}
@@ -602,15 +598,14 @@ static int hwmon_pmu__config_term(const struct hwmon_pmu *hwm,
* Item is of form <type>_ which means we should
* match <type>_<label>.
*/
- struct hashmap_entry *cur;
- size_t bkt;
+ long key_val;
+ struct hwmon_pmu_event_value *value;
attr->config = 0;
- hashmap__for_each_entry((&hwm->events), cur, bkt) {
+ hashmap__for_each(&hwm->events, key_val, value) {
union hwmon_pmu_event_key key = {
- .type_and_num = cur->key,
+ .type_and_num = key_val,
};
- struct hwmon_pmu_event_value *value = cur->pvalue;
if (key.type == type && value->name &&
!strcasecmp(term->config, value->name)) {
--
2.56.1.703.gf848ed807c-goog
^ permalink raw reply related [flat|nested] 53+ messages in thread
* [PATCH v1 18/26] perf s390: Use generic hashmap for counter name cache
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
` (16 preceding siblings ...)
2026-11-13 21:48 ` [PATCH v1 17/26] perf hwmon_pmu: Use generic hashmap for hwmon events Ian Rogers
@ 2026-11-13 21:48 ` 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
` (7 subsequent siblings)
25 siblings, 1 reply; 53+ messages in thread
From: Ian Rogers @ 2026-11-13 21:48 UTC (permalink / raw)
To: Peter Zijlstra, Ingo Molnar, Arnaldo Carvalho de Melo,
Namhyung Kim
Cc: Jiri Olsa, Ian Rogers, Adrian Hunter, James Clark,
linux-perf-users, linux-kernel
Convert the counter name cache in util/s390-sample-raw.c to
DECLARE_HASHMAP(s390_counter_name_map, long, char *).
Signed-off-by: Ian Rogers <irogers@google.com>
---
tools/perf/util/s390-sample-raw.c | 7 +++++--
1 file changed, 5 insertions(+), 2 deletions(-)
diff --git a/tools/perf/util/s390-sample-raw.c b/tools/perf/util/s390-sample-raw.c
index e0fac2aa15ed..c9afc4bc4a39 100644
--- a/tools/perf/util/s390-sample-raw.c
+++ b/tools/perf/util/s390-sample-raw.c
@@ -170,6 +170,8 @@ static bool get_counter_name_hashmap_equal_fn(long key1, long key2, void *ctx __
return key1 == key2;
}
+DECLARE_HASHMAP(s390_counter_name_map, long, char *);
+
/* Scan the PMU and extract the logical name of a counter from the event. Input
* is the counter set and counter number with in the set. Construct the event
* number and use this as key. If they match return the name of this counter.
@@ -177,7 +179,7 @@ static bool get_counter_name_hashmap_equal_fn(long key1, long key2, void *ctx __
*/
static char *get_counter_name(int set, int nr, struct perf_pmu *pmu)
{
- static struct hashmap *cache;
+ static struct s390_counter_name_map *cache;
static struct perf_pmu *cache_pmu;
long cache_key = get_counterset_start(set) + nr;
struct get_counter_name_data data = {
@@ -198,7 +200,8 @@ static char *get_counter_name(int set, int nr, struct perf_pmu *pmu)
result = strdup(data.result ?: "<unknown>");
if (cache_pmu == NULL) {
- struct hashmap *tmp = hashmap__new(get_counter_name_hash_fn,
+ struct s390_counter_name_map *tmp =
+ s390_counter_name_map__new(get_counter_name_hash_fn,
get_counter_name_hashmap_equal_fn,
/*ctx=*/NULL);
--
2.56.1.703.gf848ed807c-goog
^ permalink raw reply related [flat|nested] 53+ messages in thread
* [PATCH v1 19/26] perf threads: Use generic hashmap for thread shards
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
` (17 preceding siblings ...)
2026-11-13 21:48 ` [PATCH v1 18/26] perf s390: Use generic hashmap for counter name cache Ian Rogers
@ 2026-11-13 21:48 ` 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
` (6 subsequent siblings)
25 siblings, 1 reply; 53+ messages in thread
From: Ian Rogers @ 2026-11-13 21:48 UTC (permalink / raw)
To: Peter Zijlstra, Ingo Molnar, Arnaldo Carvalho de Melo,
Namhyung Kim
Cc: Jiri Olsa, Ian Rogers, Adrian Hunter, James Clark,
linux-perf-users, linux-kernel
Convert threads_table_entry::shard in util/threads.{c,h} to
DECLARE_HASHMAP(threads_map, pid_t, struct thread *) and replace
hashmap__for_each_entry{,_safe}() loops with hashmap__for_each{,_safe}().
Signed-off-by: Ian Rogers <irogers@google.com>
---
tools/perf/util/threads.c | 16 ++++++++--------
tools/perf/util/threads.h | 4 +++-
2 files changed, 11 insertions(+), 9 deletions(-)
diff --git a/tools/perf/util/threads.c b/tools/perf/util/threads.c
index 98cf7edaf748..c2c675d064b3 100644
--- a/tools/perf/util/threads.c
+++ b/tools/perf/util/threads.c
@@ -136,15 +136,15 @@ void threads__remove_all_threads(struct threads *threads)
{
for (int i = 0; i < THREADS__TABLE_SIZE; i++) {
struct threads_table_entry *table = &threads->table[i];
- struct hashmap_entry *cur, *tmp;
- size_t bkt;
+ pid_t tid;
+ struct thread *val;
down_write(&table->lock);
__threads_table_entry__set_last_match(table, NULL);
- hashmap__for_each_entry_safe(&table->shard, cur, tmp, bkt) {
+ hashmap__for_each_safe(&table->shard, tid, val) {
struct thread *old_value = NULL;
- if (hashmap__delete(&table->shard, cur->key, /*old_key=*/NULL, &old_value))
+ if (hashmap__delete(&table->shard, tid, /*old_key=*/NULL, &old_value))
thread__put(old_value);
}
up_write(&table->lock);
@@ -171,12 +171,12 @@ int threads__for_each_thread(struct threads *threads,
{
for (int i = 0; i < THREADS__TABLE_SIZE; i++) {
struct threads_table_entry *table = &threads->table[i];
- struct hashmap_entry *cur;
- size_t bkt;
+ pid_t tid;
+ struct thread *th;
down_read(&table->lock);
- hashmap__for_each_entry(&table->shard, cur, bkt) {
- int rc = fn((struct thread *)cur->pvalue, data);
+ hashmap__for_each(&table->shard, tid, th) {
+ int rc = fn(th, data);
if (rc != 0) {
up_read(&table->lock);
diff --git a/tools/perf/util/threads.h b/tools/perf/util/threads.h
index da68d2223f18..059d7a617e63 100644
--- a/tools/perf/util/threads.h
+++ b/tools/perf/util/threads.h
@@ -10,9 +10,11 @@ struct thread;
#define THREADS__TABLE_BITS 3
#define THREADS__TABLE_SIZE (1 << THREADS__TABLE_BITS)
+DECLARE_HASHMAP(threads_map, pid_t, struct thread *);
+
struct threads_table_entry {
/* Key is tid, value is struct thread. */
- struct hashmap shard;
+ struct threads_map shard;
struct rw_semaphore lock;
struct thread *last_match;
};
--
2.56.1.703.gf848ed807c-goog
^ permalink raw reply related [flat|nested] 53+ messages in thread
* [PATCH v1 20/26] perf annotate: Use generic hashmap for sym_samples
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
` (18 preceding siblings ...)
2026-11-13 21:48 ` [PATCH v1 19/26] perf threads: Use generic hashmap for thread shards Ian Rogers
@ 2026-11-13 21:48 ` 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
` (5 subsequent siblings)
25 siblings, 1 reply; 53+ messages in thread
From: Ian Rogers @ 2026-11-13 21:48 UTC (permalink / raw)
To: Peter Zijlstra, Ingo Molnar, Arnaldo Carvalho de Melo,
Namhyung Kim
Cc: Jiri Olsa, Ian Rogers, Adrian Hunter, James Clark,
linux-perf-users, linux-kernel
Convert annotated_source::samples in util/annotate.{c,h} to
DECLARE_HASHMAP(sym_samples_map, long, struct sym_hist_entry *) and use
hashmap__for_each() when freeing histogram entries.
Signed-off-by: Ian Rogers <irogers@google.com>
---
tools/perf/util/annotate.c | 20 ++++++++++----------
tools/perf/util/annotate.h | 5 ++++-
2 files changed, 14 insertions(+), 11 deletions(-)
diff --git a/tools/perf/util/annotate.c b/tools/perf/util/annotate.c
index d11add6424dc..c6325040452f 100644
--- a/tools/perf/util/annotate.c
+++ b/tools/perf/util/annotate.c
@@ -112,16 +112,16 @@ static struct annotated_source *annotated_source__new(void)
static __maybe_unused void annotated_source__delete(struct annotated_source *src)
{
- struct hashmap_entry *cur;
- size_t bkt;
+ long key;
+ struct sym_hist_entry *val;
if (src == NULL)
return;
if (src->samples) {
- hashmap__for_each_entry(src->samples, cur, bkt)
- zfree(&cur->pvalue);
- hashmap__free(src->samples);
+ hashmap__for_each(src->samples, key, val)
+ free(val);
+ sym_samples_map__free(src->samples);
}
zfree(&src->histograms);
free(src);
@@ -136,7 +136,7 @@ static int annotated_source__alloc_histograms(struct annotated_source *src,
if (src->histograms == NULL)
return -1;
- src->samples = hashmap__new(sym_hist_hash, sym_hist_equal, NULL);
+ src->samples = sym_samples_map__new(sym_hist_hash, sym_hist_equal, NULL);
if (!src->samples)
zfree(&src->histograms);
@@ -163,11 +163,11 @@ void symbol__annotate_zero_histograms(struct symbol *sym)
memset(notes->src->histograms, 0,
notes->src->nr_histograms * sizeof(*notes->src->histograms));
if (notes->src->samples) {
- struct hashmap_entry *cur;
- size_t bkt;
+ long key;
+ struct sym_hist_entry *val;
- hashmap__for_each_entry(notes->src->samples, cur, bkt)
- zfree(&cur->pvalue);
+ hashmap__for_each(notes->src->samples, key, val)
+ free(val);
hashmap__clear(notes->src->samples);
}
}
diff --git a/tools/perf/util/annotate.h b/tools/perf/util/annotate.h
index 1b47c1156b9a..495c8602864c 100644
--- a/tools/perf/util/annotate.h
+++ b/tools/perf/util/annotate.h
@@ -312,10 +312,13 @@ struct cyc_hist {
* presented. It is deallocated right after symbol__{tui,tty,etc}_annotate
* returns.
*/
+struct sym_hist_entry;
+DECLARE_HASHMAP(sym_samples_map, long, struct sym_hist_entry *);
+
struct annotated_source {
struct list_head source;
struct sym_hist *histograms;
- struct hashmap *samples;
+ struct sym_samples_map *samples;
int nr_histograms;
int nr_events;
int nr_entries;
--
2.56.1.703.gf848ed807c-goog
^ permalink raw reply related [flat|nested] 53+ messages in thread
* [PATCH v1 21/26] perf annotate: Use generic hashmap for browser type_hash
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
` (19 preceding siblings ...)
2026-11-13 21:48 ` [PATCH v1 20/26] perf annotate: Use generic hashmap for sym_samples Ian Rogers
@ 2026-11-13 21:48 ` 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
` (4 subsequent siblings)
25 siblings, 1 reply; 53+ messages in thread
From: Ian Rogers @ 2026-11-13 21:48 UTC (permalink / raw)
To: Peter Zijlstra, Ingo Molnar, Arnaldo Carvalho de Melo,
Namhyung Kim
Cc: Jiri Olsa, Ian Rogers, Adrian Hunter, James Clark,
linux-perf-users, linux-kernel
Convert annotate_browser::type_hash and annotation_print_data::type_hash
in util/annotate.{c,h} and ui/browsers/annotate.c to
DECLARE_HASHMAP(type_hash_map, long, struct type_hash_entry *).
Signed-off-by: Ian Rogers <irogers@google.com>
---
tools/perf/ui/browsers/annotate.c | 18 +++++++++---------
tools/perf/util/annotate.c | 5 -----
tools/perf/util/annotate.h | 8 +++++++-
3 files changed, 16 insertions(+), 15 deletions(-)
diff --git a/tools/perf/ui/browsers/annotate.c b/tools/perf/ui/browsers/annotate.c
index 8b0c933caf2e..7899c3e2b07a 100644
--- a/tools/perf/ui/browsers/annotate.c
+++ b/tools/perf/ui/browsers/annotate.c
@@ -39,7 +39,7 @@ struct annotate_browser {
struct hist_entry *he;
struct debuginfo *dbg;
struct evsel *evsel;
- struct hashmap *type_hash;
+ struct type_hash_map *type_hash;
bool searching_backwards;
char search_bf[128];
};
@@ -1131,8 +1131,8 @@ static int annotate_browser__run(struct annotate_browser *browser,
if (browser->dbg == NULL)
browser->dbg = dso__debuginfo(map__dso(ms->map));
if (browser->type_hash == NULL) {
- browser->type_hash = hashmap__new(type_hash, type_equal,
- /*ctx=*/NULL);
+ browser->type_hash = type_hash_map__new(type_hash, type_equal,
+ /*ctx=*/NULL);
}
annotate_browser__show(browser, title, help);
annotate_browser__debuginfo_warning(browser);
@@ -1233,7 +1233,7 @@ int __hist_entry__tui_annotate(struct hist_entry *he, struct map_symbol *ms,
if (annotate_opts.code_with_type) {
browser.dbg = dso__debuginfo(dso);
- browser.type_hash = hashmap__new(type_hash, type_equal, /*ctx=*/NULL);
+ browser.type_hash = type_hash_map__new(type_hash, type_equal, /*ctx=*/NULL);
}
browser.b.width = notes->src->widths.max_line_len +
@@ -1265,12 +1265,12 @@ int __hist_entry__tui_annotate(struct hist_entry *he, struct map_symbol *ms,
debuginfo__delete(browser.dbg);
if (browser.type_hash) {
- struct hashmap_entry *cur;
- size_t bkt;
+ long key;
+ struct type_hash_entry *val;
- hashmap__for_each_entry(browser.type_hash, cur, bkt)
- zfree(&cur->pvalue);
- hashmap__free(browser.type_hash);
+ hashmap__for_each(browser.type_hash, key, val)
+ free(val);
+ type_hash_map__free(browser.type_hash);
}
if (not_annotated && !notes->src->tried_source)
diff --git a/tools/perf/util/annotate.c b/tools/perf/util/annotate.c
index c6325040452f..ee47dcdc1f18 100644
--- a/tools/perf/util/annotate.c
+++ b/tools/perf/util/annotate.c
@@ -2075,11 +2075,6 @@ int annotation_br_cntr_entry(char **str, int br_cntr_nr,
return -ENOMEM;
}
-struct type_hash_entry {
- struct annotated_data_type *type;
- int offset;
-};
-
static int disasm_line__snprint_type_info(struct disasm_line *dl,
char *buf, int len,
struct annotation_print_data *apd)
diff --git a/tools/perf/util/annotate.h b/tools/perf/util/annotate.h
index 495c8602864c..2757fb07d4ec 100644
--- a/tools/perf/util/annotate.h
+++ b/tools/perf/util/annotate.h
@@ -202,13 +202,19 @@ struct annotation_write_ops {
void (*write_graph)(void *obj, int graph);
};
+struct type_hash_entry {
+ struct annotated_data_type *type;
+ int offset;
+};
+DECLARE_HASHMAP(type_hash_map, long, struct type_hash_entry *);
+
struct annotation_print_data {
struct hist_entry *he;
struct evsel *evsel;
const struct arch *arch;
struct debuginfo *dbg;
/* save data type info keyed by al->offset */
- struct hashmap *type_hash;
+ struct type_hash_map *type_hash;
/* It'll be set in hist_entry__annotate_printf() */
int addr_fmt_width;
};
--
2.56.1.703.gf848ed807c-goog
^ permalink raw reply related [flat|nested] 53+ messages in thread
* [PATCH v1 22/26] perf annotate-data: Use generic hashmap for type_hist samples
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
` (20 preceding siblings ...)
2026-11-13 21:48 ` [PATCH v1 21/26] perf annotate: Use generic hashmap for browser type_hash Ian Rogers
@ 2026-11-13 21:48 ` 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
` (3 subsequent siblings)
25 siblings, 1 reply; 53+ messages in thread
From: Ian Rogers @ 2026-11-13 21:48 UTC (permalink / raw)
To: Peter Zijlstra, Ingo Molnar, Arnaldo Carvalho de Melo,
Namhyung Kim
Cc: Jiri Olsa, Ian Rogers, Adrian Hunter, James Clark,
linux-perf-users, linux-kernel
Convert type_hist::samples in util/annotate-data.{c,h} and
ui/browsers/annotate-data.c to
DECLARE_HASHMAP(type_hist_map, int, struct type_hist_entry *).
Signed-off-by: Ian Rogers <irogers@google.com>
---
tools/perf/util/annotate-data.c | 10 +++++-----
tools/perf/util/annotate-data.h | 5 ++++-
2 files changed, 9 insertions(+), 6 deletions(-)
diff --git a/tools/perf/util/annotate-data.c b/tools/perf/util/annotate-data.c
index 2332d465f975..dabbbb34fcd8 100644
--- a/tools/perf/util/annotate-data.c
+++ b/tools/perf/util/annotate-data.c
@@ -2059,12 +2059,12 @@ static int alloc_data_type_histograms(struct annotated_data_type *adt, int nr_en
static void delete_data_type_histograms(struct annotated_data_type *adt)
{
for (int i = 0; i < adt->nr_histograms; i++) {
- struct hashmap *map = &adt->histograms[i].samples;
- struct hashmap_entry *pos, *tmp;
- size_t bkt;
+ struct type_hist_map *map = &adt->histograms[i].samples;
+ int offset;
+ struct type_hist_entry *entry;
- hashmap__for_each_entry_safe(map, pos, tmp, bkt)
- free(pos->pvalue);
+ hashmap__for_each_safe(map, offset, entry)
+ free(entry);
hashmap__clear(map);
}
diff --git a/tools/perf/util/annotate-data.h b/tools/perf/util/annotate-data.h
index 184262c73562..b5b780d26004 100644
--- a/tools/perf/util/annotate-data.h
+++ b/tools/perf/util/annotate-data.h
@@ -8,6 +8,7 @@
#include <linux/types.h>
#include "dwarf-regs.h"
#include "annotate.h"
+#include "hashmap.h"
#ifdef HAVE_LIBDW_SUPPORT
#include "debuginfo.h"
@@ -81,6 +82,8 @@ struct type_hist_entry {
u64 period;
};
+DECLARE_HASHMAP(type_hist_map, int, struct type_hist_entry *);
+
/**
* struct type_hist - Type histogram for each event
* @nr_samples: Total number of samples in this data type
@@ -90,7 +93,7 @@ struct type_hist_entry {
struct type_hist {
u64 nr_samples;
u64 period;
- struct hashmap samples;
+ struct type_hist_map samples;
};
/**
--
2.56.1.703.gf848ed807c-goog
^ permalink raw reply related [flat|nested] 53+ messages in thread
* [PATCH v1 23/26] perf inject: Use generic hashmap for aslr remap_addresses
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
` (21 preceding siblings ...)
2026-11-13 21:48 ` [PATCH v1 22/26] perf annotate-data: Use generic hashmap for type_hist samples Ian Rogers
@ 2026-11-13 21:48 ` 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
` (2 subsequent siblings)
25 siblings, 1 reply; 53+ messages in thread
From: Ian Rogers @ 2026-11-13 21:48 UTC (permalink / raw)
To: Peter Zijlstra, Ingo Molnar, Arnaldo Carvalho de Melo,
Namhyung Kim
Cc: Jiri Olsa, Ian Rogers, Adrian Hunter, James Clark,
linux-perf-users, linux-kernel
Convert aslr_tool::remap_addresses in util/aslr.c to
DECLARE_HASHMAP(remap_addresses_map, struct remap_addresses_key *, u64 *).
Signed-off-by: Ian Rogers <irogers@google.com>
---
tools/perf/util/aslr.c | 20 ++++++++++++--------
1 file changed, 12 insertions(+), 8 deletions(-)
diff --git a/tools/perf/util/aslr.c b/tools/perf/util/aslr.c
index e365f807b021..068e0250b151 100644
--- a/tools/perf/util/aslr.c
+++ b/tools/perf/util/aslr.c
@@ -6,6 +6,7 @@
#include "event.h"
#include "evsel.h"
#include "evlist.h"
+#include "hashmap.h"
#include "machine.h"
#include "map.h"
#include "thread.h"
@@ -69,6 +70,9 @@ static bool evsel_equal(long key1, long key2, void *ctx __maybe_unused)
struct process_top_address {
u64 remapped_max;
};
+
+DECLARE_HASHMAP(remap_addresses_map, struct remap_addresses_key *, u64 *);
+
struct aslr_tool {
/** @tool: The tool implemented here and a pointer to a delegate to process the data. */
struct delegate_tool tool;
@@ -77,7 +81,7 @@ struct aslr_tool {
/** @event_copy: Buffer used to create an event to pass to the delegate. */
char event_copy[PERF_SAMPLE_MAX_SIZE] __aligned(8);
/** @remap_addresses: mapping from remap_addresses_key to remapped address. */
- struct hashmap remap_addresses;
+ struct remap_addresses_map remap_addresses;
/** @top_addresses: mapping from process to max remapped address. */
struct hashmap top_addresses;
/**
@@ -1331,6 +1335,8 @@ void aslr_tool__delete(struct perf_tool *tool)
{
struct delegate_tool *del_tool;
struct aslr_tool *aslr;
+ struct remap_addresses_key *rkey;
+ u64 *rval;
struct hashmap_entry *cur;
size_t bkt;
struct rb_node *nd;
@@ -1341,13 +1347,11 @@ void aslr_tool__delete(struct perf_tool *tool)
del_tool = container_of(tool, struct delegate_tool, tool);
aslr = container_of(del_tool, struct aslr_tool, tool);
- hashmap__for_each_entry(&aslr->remap_addresses, cur, bkt) {
- struct remap_addresses_key *key = (struct remap_addresses_key *)cur->pkey;
-
- if (key)
- dso__put(key->dso);
- zfree(&cur->pkey);
- zfree(&cur->pvalue);
+ hashmap__for_each(&aslr->remap_addresses, rkey, rval) {
+ if (rkey)
+ dso__put(rkey->dso);
+ free(rkey);
+ free(rval);
}
hashmap__for_each_entry(&aslr->top_addresses, cur, bkt) {
zfree(&cur->pkey);
--
2.56.1.703.gf848ed807c-goog
^ permalink raw reply related [flat|nested] 53+ messages in thread
* [PATCH v1 24/26] perf inject: Use generic hashmap for aslr top_addresses
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
` (22 preceding siblings ...)
2026-11-13 21:48 ` [PATCH v1 23/26] perf inject: Use generic hashmap for aslr remap_addresses Ian Rogers
@ 2026-11-13 21:48 ` 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 21:48 ` [PATCH v1 26/26] perf hashmap: Remove untyped struct hashmap API Ian Rogers
25 siblings, 1 reply; 53+ messages in thread
From: Ian Rogers @ 2026-11-13 21:48 UTC (permalink / raw)
To: Peter Zijlstra, Ingo Molnar, Arnaldo Carvalho de Melo,
Namhyung Kim
Cc: Jiri Olsa, Ian Rogers, Adrian Hunter, James Clark,
linux-perf-users, linux-kernel
Convert aslr_tool::top_addresses in util/aslr.c to
DECLARE_HASHMAP(top_addresses_map, struct top_addresses_key *,
struct process_top_address *).
Signed-off-by: Ian Rogers <irogers@google.com>
---
tools/perf/util/aslr.c | 16 ++++++++++------
1 file changed, 10 insertions(+), 6 deletions(-)
diff --git a/tools/perf/util/aslr.c b/tools/perf/util/aslr.c
index 068e0250b151..f9c961775e1d 100644
--- a/tools/perf/util/aslr.c
+++ b/tools/perf/util/aslr.c
@@ -71,7 +71,10 @@ struct process_top_address {
u64 remapped_max;
};
+struct top_addresses_key;
+
DECLARE_HASHMAP(remap_addresses_map, struct remap_addresses_key *, u64 *);
+DECLARE_HASHMAP(top_addresses_map, struct top_addresses_key *, struct process_top_address *);
struct aslr_tool {
/** @tool: The tool implemented here and a pointer to a delegate to process the data. */
@@ -83,7 +86,7 @@ struct aslr_tool {
/** @remap_addresses: mapping from remap_addresses_key to remapped address. */
struct remap_addresses_map remap_addresses;
/** @top_addresses: mapping from process to max remapped address. */
- struct hashmap top_addresses;
+ struct top_addresses_map top_addresses;
/**
* @evsel_orig_attrs: mapping from evsel pointer to its original
* unstripped sample_type and registers bitmasks.
@@ -383,8 +386,7 @@ static u64 aslr_tool__findnew_mapping(struct aslr_tool *aslr,
} else {
*tk = top_addr_key;
top_val->remapped_max = remap_addr + len;
- err = hashmap__insert(&aslr->top_addresses, tk, top_val,
- HASHMAP_ADD, NULL, NULL);
+ err = hashmap__add(&aslr->top_addresses, tk, top_val);
}
if (err) {
errno = -err;
@@ -1337,6 +1339,8 @@ void aslr_tool__delete(struct perf_tool *tool)
struct aslr_tool *aslr;
struct remap_addresses_key *rkey;
u64 *rval;
+ struct top_addresses_key *tkey;
+ struct process_top_address *tval;
struct hashmap_entry *cur;
size_t bkt;
struct rb_node *nd;
@@ -1353,9 +1357,9 @@ void aslr_tool__delete(struct perf_tool *tool)
free(rkey);
free(rval);
}
- hashmap__for_each_entry(&aslr->top_addresses, cur, bkt) {
- zfree(&cur->pkey);
- zfree(&cur->pvalue);
+ hashmap__for_each(&aslr->top_addresses, tkey, tval) {
+ free(tkey);
+ free(tval);
}
hashmap__for_each_entry(&aslr->evsel_orig_attrs, cur, bkt) {
zfree(&cur->pvalue);
--
2.56.1.703.gf848ed807c-goog
^ permalink raw reply related [flat|nested] 53+ messages in thread
* [PATCH v1 25/26] perf inject: Use generic hashmap for aslr evsel_orig_attrs
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
` (23 preceding siblings ...)
2026-11-13 21:48 ` [PATCH v1 24/26] perf inject: Use generic hashmap for aslr top_addresses Ian Rogers
@ 2026-11-13 21:48 ` 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
25 siblings, 1 reply; 53+ messages in thread
From: Ian Rogers @ 2026-11-13 21:48 UTC (permalink / raw)
To: Peter Zijlstra, Ingo Molnar, Arnaldo Carvalho de Melo,
Namhyung Kim
Cc: Jiri Olsa, Ian Rogers, Adrian Hunter, James Clark,
linux-perf-users, linux-kernel
Convert aslr_tool::evsel_orig_attrs in util/aslr.c to
DECLARE_HASHMAP(evsel_orig_attrs_map, struct evsel *,
struct aslr_evsel_priv *).
Signed-off-by: Ian Rogers <irogers@google.com>
---
tools/perf/util/aslr.c | 12 ++++++------
1 file changed, 6 insertions(+), 6 deletions(-)
diff --git a/tools/perf/util/aslr.c b/tools/perf/util/aslr.c
index f9c961775e1d..8aaa685f4203 100644
--- a/tools/perf/util/aslr.c
+++ b/tools/perf/util/aslr.c
@@ -75,6 +75,7 @@ struct top_addresses_key;
DECLARE_HASHMAP(remap_addresses_map, struct remap_addresses_key *, u64 *);
DECLARE_HASHMAP(top_addresses_map, struct top_addresses_key *, struct process_top_address *);
+DECLARE_HASHMAP(evsel_orig_attrs_map, struct evsel *, struct aslr_evsel_priv *);
struct aslr_tool {
/** @tool: The tool implemented here and a pointer to a delegate to process the data. */
@@ -91,7 +92,7 @@ struct aslr_tool {
* @evsel_orig_attrs: mapping from evsel pointer to its original
* unstripped sample_type and registers bitmasks.
*/
- struct hashmap evsel_orig_attrs;
+ struct evsel_orig_attrs_map evsel_orig_attrs;
};
static const pid_t kernel_pid = -1;
@@ -1341,8 +1342,8 @@ void aslr_tool__delete(struct perf_tool *tool)
u64 *rval;
struct top_addresses_key *tkey;
struct process_top_address *tval;
- struct hashmap_entry *cur;
- size_t bkt;
+ struct evsel *ekey;
+ struct aslr_evsel_priv *eval;
struct rb_node *nd;
if (!tool)
@@ -1361,9 +1362,8 @@ void aslr_tool__delete(struct perf_tool *tool)
free(tkey);
free(tval);
}
- hashmap__for_each_entry(&aslr->evsel_orig_attrs, cur, bkt) {
- zfree(&cur->pvalue);
- }
+ hashmap__for_each(&aslr->evsel_orig_attrs, ekey, eval)
+ free(eval);
hashmap__clear(&aslr->remap_addresses);
hashmap__clear(&aslr->top_addresses);
--
2.56.1.703.gf848ed807c-goog
^ permalink raw reply related [flat|nested] 53+ messages in thread
* [PATCH v1 26/26] perf hashmap: Remove untyped struct hashmap API
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
` (24 preceding siblings ...)
2026-11-13 21:48 ` [PATCH v1 25/26] perf inject: Use generic hashmap for aslr evsel_orig_attrs Ian Rogers
@ 2026-11-13 21:48 ` Ian Rogers
2026-11-13 22:01 ` sashiko-bot
25 siblings, 1 reply; 53+ messages in thread
From: Ian Rogers @ 2026-11-13 21:48 UTC (permalink / raw)
To: Peter Zijlstra, Ingo Molnar, Arnaldo Carvalho de Melo,
Namhyung Kim
Cc: Jiri Olsa, Ian Rogers, Adrian Hunter, James Clark,
linux-perf-users, linux-kernel
Now that all hashmap users in tools/perf have been migrated to
DECLARE_HASHMAP(), remove the legacy untyped struct hashmap wrapper,
hashmap__new(), __hashmap__new(), __hashmap__free(), the
untyped-hashmap compatibility branches in hashmap__insert/find/delete,
and the unused hashmap__for_each_entry* macros from util/hashmap.h, and
rename struct hashmap_entry to struct __hashmap_entry.
Signed-off-by: Ian Rogers <irogers@google.com>
---
tools/perf/util/hashmap.c | 75 +++++++----------
tools/perf/util/hashmap.h | 173 +++++++-------------------------------
2 files changed, 62 insertions(+), 186 deletions(-)
diff --git a/tools/perf/util/hashmap.c b/tools/perf/util/hashmap.c
index f6bfaeaee9d7..946105919197 100644
--- a/tools/perf/util/hashmap.c
+++ b/tools/perf/util/hashmap.c
@@ -21,15 +21,15 @@
/* start with 4 buckets */
#define HASHMAP_MIN_CAP_BITS 2
-static void __hashmap__add_entry(struct hashmap_entry **pprev,
- struct hashmap_entry *entry)
+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)
+static void __hashmap__del_entry(struct __hashmap_entry **pprev,
+ struct __hashmap_entry *entry)
{
*pprev = entry->next;
entry->next = NULL;
@@ -48,42 +48,23 @@ 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 *map = malloc(sizeof(*map));
-
- if (!map) {
- errno = ENOMEM;
- return NULL;
- }
- __hashmap__init(map, hash_fn, equal_fn, ctx);
- return map;
-}
-
void __hashmap__clear(struct __hashmap *map)
{
- struct hashmap_entry *cur, *tmp;
- size_t bkt;
+ for (size_t bkt = 0; bkt < map->cap; bkt++) {
+ struct __hashmap_entry *cur = map->buckets[bkt];
+
+ while (cur) {
+ struct __hashmap_entry *next = cur->next;
- hashmap__for_each_entry_safe(map, cur, tmp, bkt) {
- free(cur);
+ free(cur);
+ cur = next;
+ }
}
free(map->buckets);
map->buckets = NULL;
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;
@@ -102,10 +83,9 @@ static bool __hashmap__needs_to_grow(struct __hashmap *map)
static int __hashmap__grow(struct __hashmap *map)
{
- struct hashmap_entry **new_buckets;
- struct hashmap_entry *cur, *tmp;
+ struct __hashmap_entry **new_buckets;
size_t new_cap_bits, new_cap;
- size_t h, bkt;
+ size_t h;
new_cap_bits = map->cap_bits + 1;
if (new_cap_bits < HASHMAP_MIN_CAP_BITS)
@@ -116,9 +96,16 @@ static int __hashmap__grow(struct __hashmap *map)
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);
+ for (size_t bkt = 0; bkt < map->cap; bkt++) {
+ struct __hashmap_entry *cur = map->buckets[bkt];
+
+ while (cur) {
+ struct __hashmap_entry *next = cur->next;
+
+ h = hash_bits(map->hash_fn(cur->key, map->ctx), new_cap_bits);
+ __hashmap__add_entry(&new_buckets[h], cur);
+ cur = next;
+ }
}
map->cap = new_cap;
@@ -131,10 +118,10 @@ static int __hashmap__grow(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)
+ struct __hashmap_entry ***pprev,
+ struct __hashmap_entry **entry)
{
- struct hashmap_entry *cur, **prev_ptr;
+ struct __hashmap_entry *cur, **prev_ptr;
if (!map->buckets)
return false;
@@ -157,7 +144,7 @@ int __hashmap__insert(struct __hashmap *map, long key, long value,
enum hashmap_insert_strategy strategy,
void *old_key, void *old_value)
{
- struct hashmap_entry *entry;
+ struct __hashmap_entry *entry;
size_t h;
int err;
@@ -193,7 +180,7 @@ int __hashmap__insert(struct __hashmap *map, long key, long value,
h = hash_bits(map->hash_fn(key, map->ctx), map->cap_bits);
}
- entry = malloc(sizeof(struct hashmap_entry));
+ entry = malloc(sizeof(struct __hashmap_entry));
if (!entry)
return -ENOMEM;
@@ -207,7 +194,7 @@ int __hashmap__insert(struct __hashmap *map, long key, long value,
bool __hashmap__find(const struct __hashmap *map, long key, void *value)
{
- struct hashmap_entry *entry;
+ struct __hashmap_entry *entry;
size_t h;
h = hash_bits(map->hash_fn(key, map->ctx), map->cap_bits);
@@ -222,7 +209,7 @@ bool __hashmap__find(const struct __hashmap *map, long key, void *value)
bool __hashmap__delete(struct __hashmap *map, long key,
void *old_key, void *old_value)
{
- struct hashmap_entry **pprev, *entry;
+ struct __hashmap_entry **pprev, *entry;
size_t h;
if (old_key)
diff --git a/tools/perf/util/hashmap.h b/tools/perf/util/hashmap.h
index bb85a65486be..e515443d3c70 100644
--- a/tools/perf/util/hashmap.h
+++ b/tools/perf/util/hashmap.h
@@ -92,16 +92,10 @@ typedef bool (*hashmap_equal_fn)(long key1, long key2, void *ctx);
* (e.g. bool, int, pid_t) are safe from buffer overflows, endianness bugs,
* and strict-aliasing violations.
*/
-struct hashmap_entry {
- union {
- long key;
- const void *pkey;
- };
- union {
- long value;
- void *pvalue;
- };
- struct hashmap_entry *next;
+struct __hashmap_entry {
+ long key;
+ long value;
+ struct __hashmap_entry *next;
};
struct __hashmap {
@@ -109,57 +103,19 @@ struct __hashmap {
hashmap_equal_fn equal_fn;
void *ctx;
- struct hashmap_entry **buckets;
+ struct __hashmap_entry **buckets;
size_t cap;
size_t cap_bits;
size_t sz;
};
-/* 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;
- };
-};
-
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);
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)
-{
- struct hashmap *map = malloc(sizeof(*map));
-
- if (!map) {
- errno = ENOMEM;
- return NULL;
- }
- __hashmap__init(&map->map, hash_fn, equal_fn, ctx);
- return map;
-}
-
/**
* enum hashmap_insert_strategy - Strategy for hashmap__insert().
* @HASHMAP_ADD: Only add key/value if key doesn't exist yet; returns -EEXIST
@@ -195,8 +151,8 @@ bool __hashmap__find(const struct __hashmap *map, long key, void *value);
struct __hashmap_iter {
const struct __hashmap *map;
size_t bkt;
- struct hashmap_entry *cur;
- struct hashmap_entry *next;
+ struct __hashmap_entry *cur;
+ struct __hashmap_entry *next;
};
static inline struct __hashmap_iter
@@ -210,7 +166,7 @@ __hashmap_iter__init(const struct __hashmap *map)
};
}
-static inline struct hashmap_entry *
+static inline struct __hashmap_entry *
__hashmap_iter__advance(struct __hashmap_iter *it)
{
if (!it->map || !it->map->buckets)
@@ -232,14 +188,14 @@ __hashmap_iter__advance(struct __hashmap_iter *it)
struct __hashmap_key_iter {
const struct __hashmap *map;
long key;
- struct hashmap_entry *cur;
- struct hashmap_entry *next;
+ struct __hashmap_entry *cur;
+ struct __hashmap_entry *next;
};
static inline struct __hashmap_key_iter
__hashmap_key_iter__init(const struct __hashmap *map, long key)
{
- struct hashmap_entry *first = NULL;
+ struct __hashmap_entry *first = NULL;
if (map && map->buckets) {
size_t h = hash_bits(map->hash_fn(key, map->ctx), map->cap_bits);
@@ -254,11 +210,11 @@ __hashmap_key_iter__init(const struct __hashmap *map, long key)
};
}
-static inline struct hashmap_entry *
+static inline struct __hashmap_entry *
__hashmap_key_iter__advance(struct __hashmap_key_iter *it)
{
while (it->next) {
- struct hashmap_entry *entry = it->next;
+ struct __hashmap_entry *entry = it->next;
it->next = entry->next;
if (it->map->equal_fn(entry->key, it->key, it->map->ctx)) {
@@ -289,7 +245,6 @@ struct name { \
struct __hashmap map; \
key_type *__key; \
value_type *__value; \
- int __untyped; \
}; \
_Static_assert(sizeof(key_type) <= sizeof(long), \
#name " key_type exceeds sizeof(long)"); \
@@ -328,19 +283,6 @@ static inline __maybe_unused void name##__free(struct name *hmap) \
*(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().
@@ -397,25 +339,16 @@ do { \
__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) \
+ hm, k, v, okp, ovp, 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); \
+ typeof(*(hm)->__key) k = (key); \
+ typeof(*(hm)->__value) v = (value); \
+ typeof((hm)->__key) okp = (old_key); \
+ typeof((hm)->__value) ovp = (old_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); \
+ int err = __hashmap__insert(&(hm)->map, (long)k, (long)v, (strategy), \
+ okp ? &ok : NULL, ovp ? &ov : NULL); \
if (okp) \
*okp = (typeof(*(hm)->__key))ok; \
if (ovp) \
@@ -427,7 +360,6 @@ do { \
___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))
/**
@@ -503,18 +435,13 @@ do { \
#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) \
+#define ___hashmap__find(hmap, key, value_ptr, hm, k, vp, v, found) \
({ \
typeof(hmap) hm = (hmap); \
- ___hashmap__lookup_key_type(hm) k = ___hashmap__in_val(hm, key); \
+ ___hashmap__lookup_key_type(hm) k = (key); \
+ typeof((hm)->__value) vp = (value_ptr); \
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); \
+ bool found = __hashmap__find(&(hm)->map, (long)k, vp ? &v : NULL); \
if (found && vp) \
*vp = (typeof(*(hm)->__value))v; \
found; \
@@ -523,7 +450,7 @@ do { \
#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))
+ __PASTE(__v_, u), __PASTE(__found_, u))
/**
* hashmap__find() - Look up a key in a typed hashmap.
@@ -538,23 +465,15 @@ do { \
_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) \
+ hm, k, okp, ovp, ok, ov, rem) \
({ \
typeof(hmap) hm = (hmap); \
- ___hashmap__lookup_key_type(hm) k = ___hashmap__in_val(hm, key); \
+ ___hashmap__lookup_key_type(hm) k = (key); \
+ typeof((hm)->__key) okp = (old_key); \
+ typeof((hm)->__value) ovp = (old_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)); \
- 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); \
+ bool rem = __hashmap__delete(&(hm)->map, (long)k, \
+ okp ? &ok : NULL, ovp ? &ov : NULL); \
if (okp) \
*okp = (typeof(*(hm)->__key))ok; \
if (ovp) \
@@ -566,7 +485,6 @@ do { \
___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))
/**
@@ -660,33 +578,4 @@ do { \
#define hashmap__for_each_key_safe(hmap, key, val_var) \
hashmap__for_each_key(hmap, key, val_var)
-/*
- * 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)
-
-#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)
-
-#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)] \
- : NULL; \
- cur; \
- cur = cur->next) \
- if ((map)->equal_fn(cur->key, (_key), (map)->ctx))
-
-#define hashmap__for_each_key_entry_safe(map, cur, tmp, _key) \
- for (cur = (map)->buckets \
- ? (map)->buckets[hash_bits((map)->hash_fn((_key), (map)->ctx), (map)->cap_bits)] \
- : NULL; \
- cur && ({ tmp = cur->next; true; }); \
- cur = tmp) \
- if ((map)->equal_fn(cur->key, (_key), (map)->ctx))
-
#endif /* __PERF_UTIL_HASHMAP_H */
--
2.56.1.703.gf848ed807c-goog
^ permalink raw reply related [flat|nested] 53+ messages in thread
* Re: [PATCH v1 04/26] perf build: Don't check difference of perf and libbpf hashmap
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
0 siblings, 0 replies; 53+ messages in thread
From: sashiko-bot @ 2026-11-13 21:54 UTC (permalink / raw)
To: Ian Rogers; +Cc: linux-perf-users
> We want to explore diverging the perf hashmap from libbpf's to fix a
> few subtle bugs around strict aliasing (casting void ** to long *) and
> use of ERR_PTR which is non-standard in the perf codebase and a source
> of bugs such as:
> commit d05073adda0f ("perf trace: Avoid an ERR_PTR in syscall_stats")
> commit 96f202eab813 ("perf trace: Fix IS_ERR() vs NULL check bug")
> commit 9f3c16a430e8 ("perf expr: Fix return value of ids__new()")
>
> Signed-off-by: Ian Rogers <irogers@google.com>
Sashiko has reviewed this patch and found no issues.
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261113214850.3329699-1-irogers@google.com?part=4
^ permalink raw reply [flat|nested] 53+ messages in thread
* Re: [PATCH v1 03/26] perf stat: Fix per_pkg_mask key leak on hashmap__add failure
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
0 siblings, 0 replies; 53+ messages in thread
From: sashiko-bot @ 2026-11-13 21:55 UTC (permalink / raw)
To: Ian Rogers; +Cc: linux-perf-users
> In check_per_pkg(), key is allocated with malloc() before checking
> whether the (die, socket) pair is already present in per_pkg_mask. If
> hashmap__find() misses and hashmap__add() fails (e.g. -ENOMEM), key is
> never freed.
>
> Free key when hashmap__add() fails.
>
> Fixes: 034f7ee130c1 ("perf stat: Fix wrong skipping for per-die aggregation")
> Signed-off-by: Ian Rogers <irogers@google.com>
Sashiko has reviewed this patch and found no issues.
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261113214850.3329699-1-irogers@google.com?part=3
^ permalink raw reply [flat|nested] 53+ messages in thread
* Re: [PATCH v1 05/26] perf hashmap: Always build perf's hashmap and avoid libbpf symbol conflict
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
0 siblings, 0 replies; 53+ messages in thread
From: sashiko-bot @ 2026-11-13 21:55 UTC (permalink / raw)
To: Ian Rogers; +Cc: linux-perf-users
> Previously tools/perf/util/hashmap.o was only compiled when libbpf was
> disabled or linked dynamically; with static libbpf.a, perf linked
> against libbpf's internal hashmap symbols.
>
> To allow perf's hashmap implementation to diverge from libbpf's without
> colliding with libbpf.a's global hashmap symbols or churning callers:
> - Always build util/hashmap.o in tools/perf/util/Build.
> - Rename the backing C functions in util/hashmap.c to __hashmap__*
> (__hashmap__init, __hashmap__new, __hashmap__clear, __hashmap__free,
> __hashmap__size, __hashmap__capacity, __hashmap__insert,
> __hashmap__find, and __hashmap__delete, along with the internal
> __hashmap__* helpers).
> - Keep the existing struct hashmap and hashmap__* header wrappers in
> util/hashmap.h forwarding to those functions, and update the single
> direct hashmap_find() call in hwmon_pmu.c to hashmap__find().
>
> Signed-off-by: Ian Rogers <irogers@google.com>
Sashiko has reviewed this patch and found no issues.
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261113214850.3329699-1-irogers@google.com?part=5
^ permalink raw reply [flat|nested] 53+ messages in thread
* Re: [PATCH v1 01/26] perf threads: Fix uninitialized pointer put when thread removal misses
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
0 siblings, 0 replies; 53+ messages in thread
From: sashiko-bot @ 2026-11-13 21:56 UTC (permalink / raw)
To: Ian Rogers; +Cc: linux-perf-users
Thank you for your contribution! Sashiko AI review found no new issues
in this patch, but noticed 1 related pre-existing issue it does not
introduce:
- [Medium] perf tools: TOCTOU race in threads__find() [1]
--
commit dc3a93695be3841c2b7a294d1207451c21b43854
Author: Ian Rogers <irogers@google.com>
perf threads: Fix uninitialized pointer put when thread removal misses
This commit initializes old_value to NULL and checks the return value of
hashmap__delete() before calling thread__put() in threads__remove() and
threads__remove_all_threads().
> diff --git a/tools/perf/util/threads.c b/tools/perf/util/threads.c
> index 6ca0b178fb6c3..98cf7edaf7480 100644
> --- a/tools/perf/util/threads.c
> +++ b/tools/perf/util/threads.c
[ ... ]
> @@ -154,14 +154,14 @@ void threads__remove_all_threads(struct threads *threads)
> void threads__remove(struct threads *threads, struct thread *thread)
> {
> struct threads_table_entry *table = threads__table(threads, thread__tid(thread));
> - struct thread *old_value;
> + struct thread *old_value = NULL;
>
> down_write(&table->lock);
> if (table->last_match && RC_CHK_EQUAL(table->last_match, thread))
> __threads_table_entry__set_last_match(table, NULL);
>
> - hashmap__delete(&table->shard, thread__tid(thread), /*old_key=*/NULL, &old_value);
> - thread__put(old_value);
> + if (hashmap__delete(&table->shard, thread__tid(thread), /*old_key=*/NULL, &old_value))
> + thread__put(old_value);
> up_write(&table->lock);
> }
[Severity: Medium]
This problem wasn't introduced by this patch, but can a concurrent
threads__find() race with threads__remove() [1] and resurrect a
removed thread in table->last_match?
threads__find() looks up the thread under down_read(&table->lock), drops
the read lock, and then acquires down_write(&table->lock) inside
threads_table_entry__set_last_match() to update the cache:
tools/perf/util/threads.c:threads__find() {
...
down_read(&table->lock);
res = __threads_table_entry__get_last_match(table, tid);
if (!res) {
if (hashmap__find(&table->shard, tid, &res))
res = thread__get(res);
}
up_read(&table->lock);
if (res)
threads_table_entry__set_last_match(table, res);
return res;
}
If threads__remove() runs in the window after up_read(&table->lock),
threads__find() overwrites table->last_match with the deleted thread:
CPU0 CPU1
threads__find()
down_read(&table->lock)
hashmap__find(..., &res)
up_read(&table->lock)
threads__remove()
down_write(&table->lock)
__threads_table_entry__set_last_match(
table, NULL)
hashmap__delete(...)
up_write(&table->lock)
threads_table_entry__set_last_match()
down_write(&table->lock)
__threads_table_entry__set_last_match(
table, res) <-- resurrects removed thread in last_match
Subsequent lookups for that tid then return the removed thread from
table->last_match, causing stale event attribution and leaking the
cached reference until another lookup evicts it.
[1] https://sashiko.dev/#/bug/linux-4e27d490-a049-4f85-8e0b-b969e5b6a6bc
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261113214850.3329699-1-irogers@google.com?part=1
^ permalink raw reply [flat|nested] 53+ messages in thread
* Re: [PATCH v1 09/26] perf fncache: Use generic hashmap for fncache
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
0 siblings, 0 replies; 53+ messages in thread
From: sashiko-bot @ 2026-11-13 21:56 UTC (permalink / raw)
To: Ian Rogers; +Cc: linux-perf-users
> Convert fncache in tools/perf/util/fncache.c to
> DECLARE_HASHMAP(fncache_map, char *, bool) and use the type-checked
> hashmap__* wrappers, eliminating the manual long temporary conversion on
> bool lookups.
>
> Signed-off-by: Ian Rogers <irogers@google.com>
Sashiko has reviewed this patch and found no issues.
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261113214850.3329699-1-irogers@google.com?part=9
^ permalink raw reply [flat|nested] 53+ messages in thread
* Re: [PATCH v1 06/26] perf hashmap: Fix strict aliasing violations in hashmap
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
0 siblings, 0 replies; 53+ messages in thread
From: sashiko-bot @ 2026-11-13 21:56 UTC (permalink / raw)
To: Ian Rogers; +Cc: linux-perf-users
> The hashmap in perf util (copied from libbpf) contained strict
> aliasing violations. Specifically, the hashmap_cast_ptr(p) macro was
> casting pointers (such as void **) to long *, and these were
> subsequently dereferenced in functions like __hashmap__insert(),
> __hashmap__find(), and __hashmap__delete().
>
> C's strict aliasing rules (C11 6.5/7) prohibit accessing an object
> through an lvalue of an incompatible type. Dereferencing a long * to
> write to a void * object is a violation, even if they share the same
> size, as they are not compatible types. This can lead to undefined
> behavior, especially with aggressive compiler optimizations.
>
> Fix this by:
> 1. Updating __hashmap__insert(), __hashmap__find(), and
> __hashmap__delete() to take void * for their output parameters
> [ ... ]
> Input parameters (key and value) remain as long, as they involve value
> conversion rather than incompatible pointer dereferencing, which is safe
> under strict aliasing rules.
>
> Signed-off-by: Ian Rogers <irogers@google.com>
Sashiko has reviewed this patch and found no issues.
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261113214850.3329699-1-irogers@google.com?part=6
^ permalink raw reply [flat|nested] 53+ messages in thread
* Re: [PATCH v1 10/26] perf ftrace: Use generic hashmap for profile_hash
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
0 siblings, 0 replies; 53+ messages in thread
From: sashiko-bot @ 2026-11-13 21:57 UTC (permalink / raw)
To: Ian Rogers; +Cc: linux-perf-users
> Convert perf_ftrace::profile_hash to DECLARE_HASHMAP(ftrace_profile_map,
> char *, struct ftrace_profile_data *) and use the typed hashmap__* and
> hashmap__for_each() wrappers.
>
> Signed-off-by: Ian Rogers <irogers@google.com>
Sashiko has reviewed this patch and found no issues.
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261113214850.3329699-1-irogers@google.com?part=10
^ permalink raw reply [flat|nested] 53+ messages in thread
* Re: [PATCH v1 14/26] perf stat: Use generic hashmap for per_pkg_mask
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
0 siblings, 0 replies; 53+ messages in thread
From: sashiko-bot @ 2026-11-13 21:57 UTC (permalink / raw)
To: Ian Rogers; +Cc: linux-perf-users
Thank you for your contribution! Sashiko AI review found no new issues
in this patch, but noticed 1 related pre-existing issue it does not
introduce:
- [Medium] perf stat: static variable corruption in
process_counter_values() [1]
--
commit d021f4a2698725b4414de863610e7d415a7caa42
Author: Ian Rogers <irogers@google.com>
perf stat: Use generic hashmap for per_pkg_mask
This commit converts evsel::per_pkg_mask to use the typed hashmap wrapper
DECLARE_HASHMAP(per_pkg_mask_map, uint64_t *, bool) and updates its
allocation, iteration, and cleanup callers.
> diff --git a/tools/perf/util/stat.c b/tools/perf/util/stat.c
> index 097985da3dbb8..c502f4928f9d2 100644
> --- a/tools/perf/util/stat.c
> +++ b/tools/perf/util/stat.c
[ ... ]
> @@ -364,7 +364,7 @@ static int check_per_pkg(struct evsel *counter, struct perf_counts_values *vals,
> *skip = true;
> free(key);
> } else {
> - ret = hashmap__add(mask, key, 1);
> + ret = hashmap__add(mask, key, true);
> if (ret)
> free(key);
> }
[Severity: Medium]
This problem wasn't introduced by this patch, but can setting *skip = true
here cause evsel__compute_deltas() to corrupt the static zero struct in
process_counter_values() [1]?
When check_per_pkg() sets skip, process_counter_values() points count at a
local static struct perf_counts_values zero and passes it to
evsel__compute_deltas():
tools/perf/util/stat.c:process_counter_values() {
static struct perf_counts_values zero;
bool skip = false;
if (check_per_pkg(evsel, count, cpu_map_idx, &skip)) {
pr_err("failed to read per-pkg counter\n");
return -1;
}
if (skip)
count = &zero;
if (!evsel->snapshot)
evsel__compute_deltas(evsel, cpu_map_idx, thread, count);
...
}
evsel__compute_deltas() subtracts the previous interval's raw counts from
*count in place:
tools/perf/util/evsel.c:evsel__compute_deltas() {
...
tmp = *perf_counts(evsel->prev_raw_counts, cpu_map_idx, thread);
*perf_counts(evsel->prev_raw_counts, cpu_map_idx, thread) = *count;
count->val = count->val - tmp.val;
count->ena = count->ena - tmp.ena;
count->run = count->run - tmp.run;
}
If a CPU had non-zero counts in a previous interval and is later skipped,
subtracting tmp from count underflows the static zero struct in place.
Every subsequent skipped CPU then reuses the corrupted static struct,
producing bogus aggregated counter values for the rest of the perf stat run.
[1] https://sashiko.dev/#/bug/linux-d1692581-98d0-4148-a6ee-49bd64e01cec
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261113214850.3329699-1-irogers@google.com?part=14
^ permalink raw reply [flat|nested] 53+ messages in thread
* Re: [PATCH v1 13/26] perf lock: Use generic hashmap for slab_hash
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
0 siblings, 0 replies; 53+ messages in thread
From: sashiko-bot @ 2026-11-13 21:57 UTC (permalink / raw)
To: Ian Rogers; +Cc: linux-perf-users
> Convert slab_hash in util/bpf_lock_contention.c to
> DECLARE_HASHMAP(slab_cache_map, u32, struct slab_cache_data *) and use
> hashmap__for_each() during cleanup.
>
> Signed-off-by: Ian Rogers <irogers@google.com>
Sashiko has reviewed this patch and found no issues.
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261113214850.3329699-1-irogers@google.com?part=13
^ permalink raw reply [flat|nested] 53+ messages in thread
* Re: [PATCH v1 12/26] perf trace: Use generic hashmap for bpf-trace-summary
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
0 siblings, 0 replies; 53+ messages in thread
From: sashiko-bot @ 2026-11-13 21:58 UTC (permalink / raw)
To: Ian Rogers; +Cc: linux-perf-users
> Convert the syscall summary hashmaps in util/bpf-trace-summary.c to
> DECLARE_HASHMAP(syscall_data_map, long, struct syscall_data *) and use
> hashmap__for_each() instead of struct hashmap_entry iteration.
>
> Signed-off-by: Ian Rogers <irogers@google.com>
Sashiko has reviewed this patch and found no issues.
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261113214850.3329699-1-irogers@google.com?part=12
^ permalink raw reply [flat|nested] 53+ messages in thread
* Re: [PATCH v1 17/26] perf hwmon_pmu: Use generic hashmap for hwmon events
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
0 siblings, 0 replies; 53+ messages in thread
From: sashiko-bot @ 2026-11-13 21:58 UTC (permalink / raw)
To: Ian Rogers; +Cc: linux-perf-users
> Convert hwmon_pmu::events in util/hwmon_pmu.c to
> DECLARE_HASHMAP(hwmon_pmu_events_map, long, struct hwmon_pmu_event_value *)
> and use hashmap__for_each{,_safe}().
>
> Signed-off-by: Ian Rogers <irogers@google.com>
Sashiko has reviewed this patch and found no issues.
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261113214850.3329699-1-irogers@google.com?part=17
^ permalink raw reply [flat|nested] 53+ messages in thread
* Re: [PATCH v1 02/26] perf annotate: Fix sym_hist_entry memory leak when zeroing histograms
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
0 siblings, 0 replies; 53+ messages in thread
From: sashiko-bot @ 2026-11-13 21:59 UTC (permalink / raw)
To: Ian Rogers; +Cc: linux-perf-users
> annotated_source__samples maps keys to heap-allocated struct
> sym_hist_entry pointers, which annotated_source__delete() frees before
> destroying the hashmap. However, symbol__annotate_zero_histograms()
> called hashmap__clear(notes->src->samples) directly without freeing the
> stored sym_hist_entry values, leaking every entry whenever histograms
> are zeroed.
>
> Iterate over notes->src->samples and free each entry before calling
> hashmap__clear().
>
> Fixes: d3e7cad6f36d ("perf annotate: Add a hashmap for symbol histogram")
> Signed-off-by: Ian Rogers <irogers@google.com>
Sashiko has reviewed this patch and found no issues.
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261113214850.3329699-1-irogers@google.com?part=2
^ permalink raw reply [flat|nested] 53+ messages in thread
* Re: [PATCH v1 21/26] perf annotate: Use generic hashmap for browser type_hash
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
0 siblings, 0 replies; 53+ messages in thread
From: sashiko-bot @ 2026-11-13 21:59 UTC (permalink / raw)
To: Ian Rogers; +Cc: linux-perf-users
> Convert annotate_browser::type_hash and annotation_print_data::type_hash
> in util/annotate.{c,h} and ui/browsers/annotate.c to
> DECLARE_HASHMAP(type_hash_map, long, struct type_hash_entry *).
>
> Signed-off-by: Ian Rogers <irogers@google.com>
Sashiko has reviewed this patch and found no issues.
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261113214850.3329699-1-irogers@google.com?part=21
^ permalink raw reply [flat|nested] 53+ messages in thread
* Re: [PATCH v1 23/26] perf inject: Use generic hashmap for aslr remap_addresses
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
0 siblings, 0 replies; 53+ messages in thread
From: sashiko-bot @ 2026-11-13 21:59 UTC (permalink / raw)
To: Ian Rogers; +Cc: linux-perf-users
> Convert aslr_tool::remap_addresses in util/aslr.c to
> DECLARE_HASHMAP(remap_addresses_map, struct remap_addresses_key *, u64 *).
>
> Signed-off-by: Ian Rogers <irogers@google.com>
Sashiko has reviewed this patch and found no issues.
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261113214850.3329699-1-irogers@google.com?part=23
^ permalink raw reply [flat|nested] 53+ messages in thread
* Re: [PATCH v1 18/26] perf s390: Use generic hashmap for counter name cache
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
0 siblings, 0 replies; 53+ messages in thread
From: sashiko-bot @ 2026-11-13 21:59 UTC (permalink / raw)
To: Ian Rogers; +Cc: linux-perf-users
> Convert the counter name cache in util/s390-sample-raw.c to
> DECLARE_HASHMAP(s390_counter_name_map, long, char *).
>
> Signed-off-by: Ian Rogers <irogers@google.com>
Sashiko has reviewed this patch and found no issues.
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261113214850.3329699-1-irogers@google.com?part=18
^ permalink raw reply [flat|nested] 53+ messages in thread
* Re: [PATCH v1 24/26] perf inject: Use generic hashmap for aslr top_addresses
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
0 siblings, 0 replies; 53+ messages in thread
From: sashiko-bot @ 2026-11-13 21:59 UTC (permalink / raw)
To: Ian Rogers; +Cc: linux-perf-users
> Convert aslr_tool::top_addresses in util/aslr.c to
> DECLARE_HASHMAP(top_addresses_map, struct top_addresses_key *,
> struct process_top_address *).
>
> Signed-off-by: Ian Rogers <irogers@google.com>
Sashiko has reviewed this patch and found no issues.
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261113214850.3329699-1-irogers@google.com?part=24
^ permalink raw reply [flat|nested] 53+ messages in thread
* Re: [PATCH v1 25/26] perf inject: Use generic hashmap for aslr evsel_orig_attrs
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
0 siblings, 0 replies; 53+ messages in thread
From: sashiko-bot @ 2026-11-13 22:00 UTC (permalink / raw)
To: Ian Rogers; +Cc: linux-perf-users
> Convert aslr_tool::evsel_orig_attrs in util/aslr.c to
> DECLARE_HASHMAP(evsel_orig_attrs_map, struct evsel *,
> struct aslr_evsel_priv *).
>
> Signed-off-by: Ian Rogers <irogers@google.com>
Sashiko has reviewed this patch and found no issues.
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261113214850.3329699-1-irogers@google.com?part=25
^ permalink raw reply [flat|nested] 53+ messages in thread
* Re: [PATCH v1 22/26] perf annotate-data: Use generic hashmap for type_hist samples
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
0 siblings, 0 replies; 53+ messages in thread
From: sashiko-bot @ 2026-11-13 22:00 UTC (permalink / raw)
To: Ian Rogers; +Cc: linux-perf-users
> Convert type_hist::samples in util/annotate-data.{c,h} and
> ui/browsers/annotate-data.c to
> DECLARE_HASHMAP(type_hist_map, int, struct type_hist_entry *).
>
> Signed-off-by: Ian Rogers <irogers@google.com>
Sashiko has reviewed this patch and found no issues.
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261113214850.3329699-1-irogers@google.com?part=22
^ permalink raw reply [flat|nested] 53+ messages in thread
* Re: [PATCH v1 08/26] perf hashmap: Introduce generic types and macros
2026-11-13 21:48 ` [PATCH v1 08/26] perf hashmap: Introduce generic types and macros Ian Rogers
@ 2026-11-13 22:01 ` sashiko-bot
0 siblings, 0 replies; 53+ messages in thread
From: sashiko-bot @ 2026-11-13 22:01 UTC (permalink / raw)
To: Ian Rogers; +Cc: linux-perf-users
> 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>
Sashiko has reviewed this patch and found no issues.
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261113214850.3329699-1-irogers@google.com?part=8
^ permalink raw reply [flat|nested] 53+ messages in thread
* Re: [PATCH v1 19/26] perf threads: Use generic hashmap for thread shards
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
0 siblings, 0 replies; 53+ messages in thread
From: sashiko-bot @ 2026-11-13 22:01 UTC (permalink / raw)
To: Ian Rogers; +Cc: linux-perf-users
> Convert threads_table_entry::shard in util/threads.{c,h} to
> DECLARE_HASHMAP(threads_map, pid_t, struct thread *) and replace
> hashmap__for_each_entry{,_safe}() loops with hashmap__for_each{,_safe}().
>
> Signed-off-by: Ian Rogers <irogers@google.com>
Sashiko has reviewed this patch and found no issues.
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261113214850.3329699-1-irogers@google.com?part=19
^ permalink raw reply [flat|nested] 53+ messages in thread
* Re: [PATCH v1 07/26] perf hashmap: Remove errptr usage from hashmap
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
0 siblings, 0 replies; 53+ messages in thread
From: sashiko-bot @ 2026-11-13 22:01 UTC (permalink / raw)
To: Ian Rogers; +Cc: linux-perf-users
> The hashmap implementation in tools/perf/util was using ERR_PTR to
> return errors from hashmap__new(). This is non-standard for most of
> the perf codebase which prefers NULL on error and setting errno. As
> such it was a frequent source of bugs:
> commit d05073adda0f ("perf trace: Avoid an ERR_PTR in syscall_stats")
> commit 96f202eab813 ("perf trace: Fix IS_ERR() vs NULL check bug")
> commit 9f3c16a430e8 ("perf expr: Fix return value of ids__new()")
> commit 4d4d00dd321f ("perf tools: Update copy of libbpf's hashmap.c")
>
> Remove the dependency on linux/err.h in hashmap.c. Update
> __hashmap__new() to set errno = ENOMEM and return NULL on failure, and
> update __hashmap__free() to check for NULL instead of using
> IS_ERR_OR_NULL().
>
> Update all callers of hashmap__new() to check for NULL (including
> missing failure checks in fncache and
> perf_pmu__create_placeholder_core_pmu()), and switch IS_ERR_OR_NULL()
> checks on hashmap pointers to regular NULL checks.
>
> Signed-off-by: Ian Rogers <irogers@google.com>
Sashiko has reviewed this patch and found no issues.
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261113214850.3329699-1-irogers@google.com?part=7
^ permalink raw reply [flat|nested] 53+ messages in thread
* Re: [PATCH v1 26/26] perf hashmap: Remove untyped struct hashmap API
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
0 siblings, 0 replies; 53+ messages in thread
From: sashiko-bot @ 2026-11-13 22:01 UTC (permalink / raw)
To: Ian Rogers; +Cc: linux-perf-users
> Now that all hashmap users in tools/perf have been migrated to
> DECLARE_HASHMAP(), remove the legacy untyped struct hashmap wrapper,
> hashmap__new(), __hashmap__new(), __hashmap__free(), the
> untyped-hashmap compatibility branches in hashmap__insert/find/delete,
> and the unused hashmap__for_each_entry* macros from util/hashmap.h, and
> rename struct hashmap_entry to struct __hashmap_entry.
>
> Signed-off-by: Ian Rogers <irogers@google.com>
Sashiko has reviewed this patch and found no issues.
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261113214850.3329699-1-irogers@google.com?part=26
^ permalink raw reply [flat|nested] 53+ messages in thread
* Re: [PATCH v1 15/26] perf expr: Use generic hashmap for expr_ids
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
0 siblings, 0 replies; 53+ messages in thread
From: sashiko-bot @ 2026-11-13 22:02 UTC (permalink / raw)
To: Ian Rogers; +Cc: linux-perf-users
Thank you for your contribution! Sashiko AI review found no new issues
in this patch, but noticed 1 related pre-existing issue it does not
introduce:
- [Medium] perf metricgroup: memory leak on realloc() failure in
resolve_metric() [1]
--
commit ae2db9c3e031dce4b59bc868fc5b91f5ceff2de6
Author: Ian Rogers <irogers@google.com>
perf expr: Use generic hashmap for expr_ids
This commit converts expr_parse_ctx::ids and its helper functions to use a
typed hashmap declared with DECLARE_HASHMAP(expr_ids_map, const char *,
struct expr_id_data *).
> diff --git a/tools/perf/util/metricgroup.c b/tools/perf/util/metricgroup.c
> index 779fe56fe8d0c..41887fad49682 100644
> --- a/tools/perf/util/metricgroup.c
> +++ b/tools/perf/util/metricgroup.c
[ ... ]
> @@ -765,10 +765,10 @@ static int resolve_metric(struct list_head *metric_list,
> * Iterate all the parsed IDs and if there's a matching metric and it to
> * the pending array.
> */
> - hashmap__for_each_entry(root_metric->pctx->ids, cur, bkt) {
> + hashmap__for_each(root_metric->pctx->ids, key, val) {
> struct pmu_metric pm;
>
> - if (pmu_metrics_table__find_metric(table, pmu, cur->pkey,
> + if (pmu_metrics_table__find_metric(table, pmu, key,
> metricgroup__find_metric_callback,
> &pm) != PMU_METRICS__NOT_FOUND) {
> pending = realloc(pending,
> @@ -777,7 +777,7 @@ static int resolve_metric(struct list_head *metric_list,
> return -ENOMEM;
>
> memcpy(&pending[pending_cnt].pm, &pm, sizeof(pm));
> - pending[pending_cnt].key = cur->pkey;
> + pending[pending_cnt].key = key;
> pending_cnt++;
> }
> }
[Severity: Medium]
This problem wasn't introduced by this patch, but can a realloc() failure
in resolve_metric() leak the existing pending buffer [1]?
When resolving a metric that references multiple sub-metrics, pending is
grown one entry at a time; assigning the result of realloc() directly back
to pending overwrites the prior pointer with NULL on failure and returns
-ENOMEM without freeing it. __add_metric() has the same issue when growing
root_metric->metric_refs with realloc().
[1] https://sashiko.dev/#/bug/linux-ca3710c6-abe9-465b-8da4-8cb1ea1a2b6b
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261113214850.3329699-1-irogers@google.com?part=15
^ permalink raw reply [flat|nested] 53+ messages in thread
* Re: [PATCH v1 16/26] perf pmu: Use generic hashmap for pmu aliases
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
0 siblings, 0 replies; 53+ messages in thread
From: sashiko-bot @ 2026-11-13 22:04 UTC (permalink / raw)
To: Ian Rogers; +Cc: linux-perf-users
Thank you for your contribution! Sashiko AI review found no new issues
in this patch, but noticed 1 related pre-existing issue it does not
introduce:
- [Medium] perf pmu: memory leak of alias on error paths in
perf_pmu__new_alias() [1]
--
commit 728d5dacfc35f09b0b16561c8cccc0e78616532f
Author: Ian Rogers <irogers@google.com>
perf pmu: Use generic hashmap for pmu aliases
This commit converts perf_pmu::aliases to use a typed generic hashmap
declared with DECLARE_HASHMAP(pmu_aliases_map, ...) and updates alias
iterations to use hashmap__for_each().
> diff --git a/tools/perf/util/pmu.c b/tools/perf/util/pmu.c
> index a63fa0d9cfa59..cabc14260719d 100644
> --- a/tools/perf/util/pmu.c
> +++ b/tools/perf/util/pmu.c
[ ... ]
> @@ -676,7 +676,7 @@ static int perf_pmu__new_alias(struct perf_pmu *pmu, const char *name,
> break;
>
> }
> - hashmap__set(pmu->aliases, alias->name, alias, /*old_key=*/ NULL, &old_alias);
> + hashmap__set(pmu->aliases, alias->name, alias, /*old_key=*/NULL, &old_alias);
> perf_pmu_free_alias(old_alias);
> return 0;
> }
[Severity: Medium]
This problem wasn't introduced by this patch, but can perf_pmu__new_alias()
leak alias on its earlier error paths [1]?
tools/perf/util/pmu.c:perf_pmu__new_alias() {
...
alias = zalloc(sizeof(*alias));
if (!alias)
return -ENOMEM;
...
if (!ret && pe && pe->retirement_latency_max) {
ret = parse_double(pe->retirement_latency_max, NULL,
&alias->retirement_latency_max);
}
if (ret)
return ret;
if (val_fd < 0) {
alias->terms = strdup(val);
} else {
...
ret = io__getline(&io, &alias->terms, &line_len) < 0 ? -errno : 0;
if (ret) {
pr_err("Failed to read alias %s\n", name);
return ret;
}
...
}
If parse_double() fails on any of the retirement latency fields or
io__getline() fails when reading the sysfs alias terms, the function
returns ret directly without freeing alias.
[1] https://sashiko.dev/#/bug/linux-11ab1439-acca-4a2e-9366-3dcd430baf6e
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261113214850.3329699-1-irogers@google.com?part=16
^ permalink raw reply [flat|nested] 53+ messages in thread
* Re: [PATCH v1 11/26] perf trace: Use generic hashmap for syscall_stats
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
0 siblings, 0 replies; 53+ messages in thread
From: sashiko-bot @ 2026-11-13 22:04 UTC (permalink / raw)
To: Ian Rogers; +Cc: linux-perf-users
> Convert trace and thread_trace syscall_stats maps in builtin-trace.c to
> DECLARE_HASHMAP(syscall_stats_map, long, struct syscall_stats *) and
> replace direct struct hashmap_entry and map->sz accesses with
> hashmap__for_each() and hashmap__size().
>
> Signed-off-by: Ian Rogers <irogers@google.com>
Sashiko has reviewed this patch and found no issues.
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261113214850.3329699-1-irogers@google.com?part=11
^ permalink raw reply [flat|nested] 53+ messages in thread
* Re: [PATCH v1 20/26] perf annotate: Use generic hashmap for sym_samples
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
0 siblings, 0 replies; 53+ messages in thread
From: sashiko-bot @ 2026-11-13 22:08 UTC (permalink / raw)
To: Ian Rogers; +Cc: linux-perf-users
Thank you for your contribution! Sashiko AI review found 2 potential
issues to consider:
- [Medium] perf annotate: unused-but-set variable key in
hashmap__for_each() loops
- [Low] perf annotate: DECLARE_HASHMAP() placed inside struct
annotated_source kdoc
--
commit 0f2a111b069905a59af5949de2e5dc82c8c9ba96
Author: Ian Rogers <irogers@google.com>
perf annotate: Use generic hashmap for sym_samples
This commit converts annotated_source::samples to a typed hashmap
declared with DECLARE_HASHMAP(sym_samples_map, long,
struct sym_hist_entry *) and updates histogram entry freeing to use
hashmap__for_each().
> diff --git a/tools/perf/util/annotate.c b/tools/perf/util/annotate.c
> index d11add6424dc3..c6325040452f0 100644
> --- a/tools/perf/util/annotate.c
> +++ b/tools/perf/util/annotate.c
> @@ -112,16 +112,16 @@ static struct annotated_source *annotated_source__new(void)
>
> static __maybe_unused void annotated_source__delete(struct annotated_source *src)
> {
> - struct hashmap_entry *cur;
> - size_t bkt;
> + long key;
> + struct sym_hist_entry *val;
>
> if (src == NULL)
> return;
>
> if (src->samples) {
> - hashmap__for_each_entry(src->samples, cur, bkt)
> - zfree(&cur->pvalue);
> - hashmap__free(src->samples);
> + hashmap__for_each(src->samples, key, val)
> + free(val);
> + sym_samples_map__free(src->samples);
> }
> zfree(&src->histograms);
> free(src);
[ ... ]
> @@ -163,11 +163,11 @@ void symbol__annotate_zero_histograms(struct symbol *sym)
> memset(notes->src->histograms, 0,
> notes->src->nr_histograms * sizeof(*notes->src->histograms));
> if (notes->src->samples) {
> - struct hashmap_entry *cur;
> - size_t bkt;
> + long key;
> + struct sym_hist_entry *val;
>
> - hashmap__for_each_entry(notes->src->samples, cur, bkt)
> - zfree(&cur->pvalue);
> + hashmap__for_each(notes->src->samples, key, val)
> + free(val);
> hashmap__clear(notes->src->samples);
> }
> }
[Severity: Medium]
Does declaring key without __maybe_unused in annotated_source__delete()
and symbol__annotate_zero_histograms() break the build with
-Werror=unused-but-set-variable?
hashmap__for_each() assigns to key on each iteration, and neither
function reads it.
> diff --git a/tools/perf/util/annotate.h b/tools/perf/util/annotate.h
> index 1b47c1156b9a7..495c8602864cf 100644
> --- a/tools/perf/util/annotate.h
> +++ b/tools/perf/util/annotate.h
> @@ -312,10 +312,13 @@ struct cyc_hist {
> * presented. It is deallocated right after symbol__{tui,tty,etc}_annotate
> * returns.
> */
> +struct sym_hist_entry;
> +DECLARE_HASHMAP(sym_samples_map, long, struct sym_hist_entry *);
> +
> struct annotated_source {
> struct list_head source;
> struct sym_hist *histograms;
> - struct hashmap *samples;
> + struct sym_samples_map *samples;
[Severity: Low]
Does placing DECLARE_HASHMAP() between the struct annotated_source
kernel-doc comment and the struct definition break scripts/kernel-doc?
Moving DECLARE_HASHMAP(sym_samples_map, ...) above the kernel-doc comment
keeps the comment attached to struct annotated_source, and the forward
declaration of struct sym_hist_entry can be dropped because it is already
defined earlier in this header.
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261113214850.3329699-1-irogers@google.com?part=20
^ permalink raw reply [flat|nested] 53+ messages in thread
end of thread, other threads:[~2026-11-13 22:08 UTC | newest]
Thread overview: 53+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
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 ` [PATCH v1 08/26] perf hashmap: Introduce generic types and macros Ian Rogers
2026-11-13 22:01 ` 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
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.