From: Matt Turner <mattst88@gmail.com>
To: Peter Zijlstra <peterz@infradead.org>,
Ingo Molnar <mingo@redhat.com>,
Arnaldo Carvalho de Melo <acme@kernel.org>,
Namhyung Kim <namhyung@kernel.org>,
Mark Rutland <mark.rutland@arm.com>,
Alexander Shishkin <alexander.shishkin@linux.intel.com>,
Jiri Olsa <jolsa@kernel.org>, Ian Rogers <irogers@google.com>,
Adrian Hunter <adrian.hunter@intel.com>,
James Clark <james.clark@linaro.org>
Cc: linux-perf-users@vger.kernel.org, linux-kernel@vger.kernel.org,
Matt Turner <mattst88@gmail.com>
Subject: [PATCH 2/2] perf annotate: add Alpha instruction support
Date: Sat, 05 Sep 2026 18:54:12 -0400 [thread overview]
Message-ID: <20260905-perf-alpha-annotate-v1-2-97380433ccb6@gmail.com> (raw)
In-Reply-To: <20260905-perf-alpha-annotate-v1-0-97380433ccb6@gmail.com>
Teach perf annotate about the Alpha control-transfer instructions, so that
an Alpha perf.data gets call and jump arrows and resolved call targets,
whether it is read on Alpha or on another host.
Add tools/perf/util/annotate-arch/annotate-alpha.c with arch__new_alpha()
and an associate_instruction_ops() that classifies:
call: bsr, plus jsr and jcr as indirect calls
ret: ret
jump: br, the conditional branches beq/bne/blt/ble/bgt/bge/blbc/blbs
and fbeq/fbne/fblt/fble/fbgt/fbge, plus jmp as an indirect jump
mov: mov, fmov (objdump pseudos)
That is every mnemonic binutils can print for the branch and JSR formats.
jcr rather than jsr_coroutine, because both name the same MBR(0x1a,3)
encoding and print_insn_alpha() takes the first match in the table, where
the jcr alias has come first since the sources were imported in 1999.
bsr needs an Alpha-specific parse routine. The generic call__parse()
expects the operand string to begin with the target address, but a bsr
prints its return-address register first:
bsr t0,fffffc0001031dc0 <cserve_ena>
strtoull() then stops on the leading register name, leaving the target
address as 0, which makes call__scnprintf() fall back to printing the raw
operands and leaves target.sym unresolved so the browser cannot follow the
call. alpha_call__parse() takes the address from after the comma instead,
as s390_call__parse() does for the same reason. The PC-relative branches
need no such handling, as jump__parse() already skips up to two operands.
jsr and jmp get ins_ops that resolve no target at all. They transfer
control to a register, and their trailing operand is only a branch
prediction hint:
jsr ra,(t12),fffffc0001014ee8 <_printk>
binutils extracts that hint as a 14-bit signed field scaled by four and
prints it relative to the next instruction (extract_jhint() in alpha-opc.c,
print_insn_alpha() in alpha-dis.c), so it can name the callee only when the
callee lies within the resulting +-32KB. It also defaults to zero, which
prints as the next instruction. Of the 213750 jsr in a vmlinux built from
this tree, only 23093 hints land on a symbol; 157204 point into the middle
of an unrelated function and 33453 are that default. Parsing the hint
would therefore invent a call target for the majority of calls, so these
keep their operands, as an indirect call does elsewhere.
EM_ALPHA is 0x9026, far too large to index the e_machine-keyed
arch_new_fn[] table in arch__find(), so select arch__new_alpha explicitly
before the table lookup. Declare it in disasm.h and add the object to the
annotate-arch Build.
Disassembly itself comes from objdump/binutils, which already supports
Alpha; this provides perf's instruction-class metadata for annotation.
Tested on an EV7 Marvel, both natively and by annotating its perf.data on
an x86_64 host, over bsr to a local function, jsr through the PLT and
kernel-mode jsr; the two hosts produce identical output.
Signed-off-by: Matt Turner <mattst88@gmail.com>
---
tools/perf/util/annotate-arch/Build | 1 +
tools/perf/util/annotate-arch/annotate-alpha.c | 185 +++++++++++++++++++++++++
tools/perf/util/disasm.c | 15 +-
tools/perf/util/disasm.h | 1 +
4 files changed, 200 insertions(+), 2 deletions(-)
diff --git a/tools/perf/util/annotate-arch/Build b/tools/perf/util/annotate-arch/Build
index 23316743fdc5..0a74e1a9f17b 100644
--- a/tools/perf/util/annotate-arch/Build
+++ b/tools/perf/util/annotate-arch/Build
@@ -1,3 +1,4 @@
+perf-util-y += annotate-alpha.o
perf-util-y += annotate-arc.o
perf-util-y += annotate-arm.o
perf-util-y += annotate-arm64.o
diff --git a/tools/perf/util/annotate-arch/annotate-alpha.c b/tools/perf/util/annotate-arch/annotate-alpha.c
new file mode 100644
index 000000000000..ccb13e2338a2
--- /dev/null
+++ b/tools/perf/util/annotate-arch/annotate-alpha.c
@@ -0,0 +1,185 @@
+// SPDX-License-Identifier: GPL-2.0
+#include <stdlib.h>
+#include <string.h>
+#include <linux/compiler.h>
+#include <linux/kernel.h>
+#include <linux/zalloc.h>
+#include "../../util/disasm.h"
+#include "../../util/map.h"
+#include "../../util/maps.h"
+#include "../../util/symbol.h"
+#include "../../util/thread.h"
+
+/*
+ * Alpha control-transfer instructions, as printed by objdump:
+ *
+ * PC-relative (opcode group 0x30-0x3f), 21-bit displacement:
+ * br, bsr unconditional / to-subroutine
+ * beq bne blt ble bgt bge blbc blbs integer conditional
+ * fbeq fbne fblt fble fbgt fbge floating conditional
+ *
+ * Register-indirect (JSR group, opcode 0x1a):
+ * jmp, jsr, ret, jcr
+ *
+ * bsr/jsr (and jcr, the coroutine form, which binutils prints in preference to
+ * the jsr_coroutine spelling) save a return address, so they are calls; ret
+ * returns; everything else that transfers control is a jump.
+ *
+ * Alpha has no machine "mov"; objdump prints "mov"/"fmov" as pseudos for
+ * bis/cpys, so map them to mov_ops when present. The no-ops are deliberately
+ * left alone: nop_ops would let delete_last_nop() trim the padding gcc leaves
+ * at the end of a function, but its scnprintf() prints the literal "nop", and
+ * Alpha pads with unop (ldq_u $31) rather than nop.
+ */
+
+/*
+ * The generic call__parse() expects the target address to be the first thing
+ * in the operand string, but a bsr prints its return-address register first:
+ *
+ * bsr t0,fffffc0001031dc0 <cserve_ena>
+ *
+ * so take the address from after the comma. Without this the address comes
+ * out as 0, and neither the callee symbol nor the annotation browser's
+ * "go to target" work.
+ */
+static int alpha_call__parse(const struct arch *arch, struct ins_operands *ops,
+ struct map_symbol *ms,
+ struct disasm_line *dl __maybe_unused)
+{
+ char *endptr, *tok, *name;
+ struct map *map = ms->map;
+ struct addr_map_symbol target;
+
+ tok = strchr(ops->raw, ',');
+ if (tok == NULL)
+ return -1;
+
+ ops->target.addr = strtoull(tok + 1, &endptr, 16);
+ if (endptr == tok + 1)
+ return -1;
+
+ /* A stripped object has no "<symbol>" to name the target with. */
+ name = strchr(endptr, '<');
+ if (name == NULL)
+ goto find_target;
+
+ name++;
+
+ if (arch->objdump.skip_functions_char &&
+ strchr(name, arch->objdump.skip_functions_char))
+ return -1;
+
+ tok = strchr(name, '>');
+ if (tok == NULL)
+ return -1;
+
+ *tok = '\0';
+ ops->target.name = strdup(name);
+ *tok = '>';
+
+ if (ops->target.name == NULL)
+ return -1;
+
+find_target:
+ target = (struct addr_map_symbol) {
+ .ms = { .map = map__get(map), },
+ .addr = map__objdump_2mem(map, ops->target.addr),
+ };
+
+ if (maps__find_ams(thread__maps(ms->thread), &target) == 0 &&
+ map__rip_2objdump(target.ms.map,
+ map__map_ip(target.ms.map, target.addr)) == ops->target.addr)
+ ops->target.sym = target.ms.sym;
+
+ addr_map_symbol__exit(&target);
+ return 0;
+}
+
+static const struct ins_ops alpha_call_ops = {
+ .parse = alpha_call__parse,
+ .scnprintf = call__scnprintf,
+ .is_call = true,
+};
+
+/*
+ * jsr and jmp transfer control to a register, and their trailing operand is
+ * only a branch prediction hint:
+ *
+ * jsr ra,(t12),fffffc0001014ee8 <_printk>
+ *
+ * binutils extracts that hint as a 14-bit signed field scaled by four and
+ * prints it relative to the next instruction (extract_jhint() in alpha-opc.c,
+ * print_insn_alpha() in alpha-dis.c), so it can name the callee only when the
+ * callee lies within the resulting +-32KB. It also defaults to zero, which
+ * prints as the next instruction. Most hints are therefore not the callee at
+ * all, and parsing one would invent a call target, so these resolve no target
+ * and keep their operands, as an indirect call does elsewhere. The hint on
+ * jcr is not even an address.
+ */
+static const struct ins_ops alpha_indirect_call_ops = {
+ .scnprintf = ins__raw_scnprintf,
+ .is_call = true,
+};
+
+static const struct ins_ops alpha_indirect_jump_ops = {
+ .scnprintf = ins__raw_scnprintf,
+ .is_jump = true,
+};
+
+static int is_alpha_cond_branch(const char *name)
+{
+ static const char *const branches[] = {
+ "beq", "bne", "blt", "ble", "bgt", "bge", "blbc", "blbs",
+ "fbeq", "fbne", "fblt", "fble", "fbgt", "fbge",
+ };
+ unsigned int i;
+
+ for (i = 0; i < ARRAY_SIZE(branches); i++) {
+ if (!strcmp(name, branches[i]))
+ return 1;
+ }
+ return 0;
+}
+
+static const struct ins_ops *alpha__associate_instruction_ops(struct arch *arch, const char *name)
+{
+ const struct ins_ops *ops = NULL;
+
+ if (!strcmp(name, "bsr")) {
+ ops = &alpha_call_ops;
+ } else if (!strcmp(name, "jsr") ||
+ !strcmp(name, "jcr")) {
+ ops = &alpha_indirect_call_ops;
+ } else if (!strcmp(name, "ret")) {
+ ops = &ret_ops;
+ } else if (!strcmp(name, "jmp")) {
+ ops = &alpha_indirect_jump_ops;
+ } else if (!strcmp(name, "br") ||
+ is_alpha_cond_branch(name)) {
+ ops = &jump_ops;
+ } else if (!strcmp(name, "mov") ||
+ !strcmp(name, "fmov")) {
+ ops = &mov_ops;
+ }
+
+ if (ops)
+ arch__associate_ins_ops(arch, name, ops);
+
+ return ops;
+}
+
+const struct arch *arch__new_alpha(const struct e_machine_and_e_flags *id,
+ const char *cpuid __maybe_unused)
+{
+ struct arch *arch = zalloc(sizeof(*arch));
+
+ if (!arch)
+ return NULL;
+
+ arch->name = "alpha";
+ arch->id = *id;
+ arch->associate_instruction_ops = alpha__associate_instruction_ops;
+ /* objdump emits no comments for Alpha; '#' is what the assembler uses. */
+ arch->objdump.comment_char = '#';
+ return arch;
+}
diff --git a/tools/perf/util/disasm.c b/tools/perf/util/disasm.c
index 6cfdbabbb8c7..49c206a88eec 100644
--- a/tools/perf/util/disasm.c
+++ b/tools/perf/util/disasm.c
@@ -161,6 +161,8 @@ const struct arch *arch__find(uint16_t e_machine, uint32_t e_flags, const char *
.e_flags = e_flags,
};
const struct arch *result = NULL, **tmp;
+ const struct arch *(*new_fn)(const struct e_machine_and_e_flags *id,
+ const char *cpuid) = NULL;
if (num_archs > 0) {
tmp = bsearch(&key, archs, num_archs, sizeof(*archs), arch__key_cmp);
@@ -171,7 +173,16 @@ const struct arch *arch__find(uint16_t e_machine, uint32_t e_flags, const char *
if (result)
return result;
- if (e_machine >= ARRAY_SIZE(arch_new_fn) || arch_new_fn[e_machine] == NULL) {
+ /*
+ * EM_ALPHA (0x9026) is far too large to index arch_new_fn[], so it is
+ * selected explicitly; everything else uses the e_machine-indexed table.
+ */
+ if (e_machine == EM_ALPHA)
+ new_fn = arch__new_alpha;
+ else if (e_machine < ARRAY_SIZE(arch_new_fn))
+ new_fn = arch_new_fn[e_machine];
+
+ if (new_fn == NULL) {
errno = ENOTSUP;
return NULL;
}
@@ -182,7 +193,7 @@ const struct arch *arch__find(uint16_t e_machine, uint32_t e_flags, const char *
archs = tmp;
- result = arch_new_fn[e_machine](&key, cpuid);
+ result = new_fn(&key, cpuid);
if (!result) {
pr_err("%s: failed to initialize %u arch priv area\n",
__func__, e_machine);
diff --git a/tools/perf/util/disasm.h b/tools/perf/util/disasm.h
index 25756e3f47e4..adbdbf8f1f35 100644
--- a/tools/perf/util/disasm.h
+++ b/tools/perf/util/disasm.h
@@ -122,6 +122,7 @@ extern const struct ins_ops ret_ops;
int arch__associate_ins_ops(struct arch *arch, const char *name, const struct ins_ops *ops);
+const struct arch *arch__new_alpha(const struct e_machine_and_e_flags *id, const char *cpuid);
const struct arch *arch__new_arc(const struct e_machine_and_e_flags *id, const char *cpuid);
const struct arch *arch__new_arm(const struct e_machine_and_e_flags *id, const char *cpuid);
const struct arch *arch__new_arm64(const struct e_machine_and_e_flags *id, const char *cpuid);
--
2.54.0
next prev parent reply other threads:[~2026-09-05 22:54 UTC|newest]
Thread overview: 8+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-09-05 22:54 [PATCH 0/2] perf: Alpha annotation support Matt Turner
2026-09-05 22:54 ` [PATCH 1/2] perf thread: Fix live-session detection in thread__e_machine() Matt Turner
2026-09-05 23:05 ` sashiko-bot
2026-09-08 17:34 ` Ian Rogers
2026-09-05 22:54 ` Matt Turner [this message]
2026-09-05 22:59 ` [PATCH 2/2] perf annotate: add Alpha instruction support sashiko-bot
2026-09-08 17:35 ` Ian Rogers
2026-09-08 21:11 ` Arnaldo Carvalho de Melo
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20260905-perf-alpha-annotate-v1-2-97380433ccb6@gmail.com \
--to=mattst88@gmail.com \
--cc=acme@kernel.org \
--cc=adrian.hunter@intel.com \
--cc=alexander.shishkin@linux.intel.com \
--cc=irogers@google.com \
--cc=james.clark@linaro.org \
--cc=jolsa@kernel.org \
--cc=linux-kernel@vger.kernel.org \
--cc=linux-perf-users@vger.kernel.org \
--cc=mark.rutland@arm.com \
--cc=mingo@redhat.com \
--cc=namhyung@kernel.org \
--cc=peterz@infradead.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is 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.