[llvm] [llvm-exegesis] Add RawPerfEvent raw PMU counter path in createCounter (PR #208364)
Maxim Potapchik via llvm-commits
llvm-commits at lists.llvm.org
Wed Jul 8 18:49:09 PDT 2026
https://github.com/MaximPotapchik created https://github.com/llvm/llvm-project/pull/208364
Introduces RawPerfEvent, a PerfEvent derived class that constructs perf_event_attr directly from EventSelect and UMask, bypassing pfm_get_os_event_encoding for cycle and uops events.
createCounter routes to RawPerfEvent when PfmCountersInfo contains a non-negative EventSelect for the cycle or uops counter slot, falling back to the existing libpfm symbolic path otherwise. Extended createBenchmarkRunner gate checks to allow raw-only counters in latency and uops modes.
This wiring is opt-in. Existing behavior is unchanged.
Adds unit tests covering RawPerfEvent construction, config field encoding, and kernel exclusion flag correctness.
Validated on AMD Zen2: latency measurements via the raw path match the libpfm symbolic path on identical hardware.
Towards #187930
>From 5c367ed12b4a8aed378ffa87f640ff7c6210ee6b Mon Sep 17 00:00:00 2001
From: MaximPotapchik <maximp1012 at gmail.com>
Date: Thu, 9 Jul 2026 02:03:40 +0200
Subject: [PATCH] [llvm-exegesis] Add RawPerfEvent raw PMU counter path in
createCounter
Introduces RawPerfEvent, a PerfEvent derived class that constructs
perf_event_attr directly from EventSelect and UMask, bypassing
pfm_get_os_event_encoding for cycle and uops events.
createCounter routes to RawPerfEvent when PfmCountersInfo contains
a non-negative EventSelect for the cycle or uops counter slot,
falling back to the existing libpfm symbolic path otherwise.
Extended createBenchmarkRunner gate checks to allow raw-only
counters in latency and uops modes.
This wiring is opt-in. Existing behavior is unchanged.
Adds unit tests covering RawPerfEvent construction, config field
encoding, and kernel exclusion flag correctness.
Validated on AMD Zen2: latency measurements via the raw path match
the libpfm symbolic path on identical hardware.
Towards #187930
---
llvm/tools/llvm-exegesis/lib/PerfHelper.cpp | 13 ++++
llvm/tools/llvm-exegesis/lib/PerfHelper.h | 8 +++
llvm/tools/llvm-exegesis/lib/Target.cpp | 46 +++++++++++---
.../tools/llvm-exegesis/CMakeLists.txt | 1 +
.../tools/llvm-exegesis/PerfHelperTest.cpp | 60 +++++++++++++++++++
5 files changed, 120 insertions(+), 8 deletions(-)
create mode 100644 llvm/unittests/tools/llvm-exegesis/PerfHelperTest.cpp
diff --git a/llvm/tools/llvm-exegesis/lib/PerfHelper.cpp b/llvm/tools/llvm-exegesis/lib/PerfHelper.cpp
index 585ef0624ca82..b12712981f5fa 100644
--- a/llvm/tools/llvm-exegesis/lib/PerfHelper.cpp
+++ b/llvm/tools/llvm-exegesis/lib/PerfHelper.cpp
@@ -74,6 +74,19 @@ PerfEvent::PerfEvent(StringRef PfmEventString)
FullQualifiedEventString = PfmEventString;
}
+RawPerfEvent::RawPerfEvent(int EventSelect, int UMask) {
+#ifdef HAVE_LIBPFM
+ EventString = ("raw:" + Twine(EventSelect) + ":" + Twine(UMask)).str();
+ FullQualifiedEventString = EventString;
+ Attr = new perf_event_attr();
+ Attr->size = sizeof(*Attr);
+ Attr->type = PERF_TYPE_RAW;
+ Attr->config = (UMask << 8) | EventSelect;
+ Attr->exclude_kernel = 1;
+ Attr->exclude_hv = 1;
+#endif
+}
+
void PerfEvent::initRealEvent(StringRef PfmEventString) {
#ifdef HAVE_LIBPFM
char *Fstr = nullptr;
diff --git a/llvm/tools/llvm-exegesis/lib/PerfHelper.h b/llvm/tools/llvm-exegesis/lib/PerfHelper.h
index 744e3c2994515..daa7b2ec214e3 100644
--- a/llvm/tools/llvm-exegesis/lib/PerfHelper.h
+++ b/llvm/tools/llvm-exegesis/lib/PerfHelper.h
@@ -76,6 +76,14 @@ class PerfEvent {
void initRealEvent(StringRef PfmEventString);
};
+// A PerfEvent that bypasses libpfm and encodes the counter directly using
+// raw PMU event select and unit mask values. Currently only used with cycles
+// and uops when fields are present in PfmCountersInfo.
+class RawPerfEvent : public PerfEvent {
+public:
+ explicit RawPerfEvent(int EventSelect, int UMask);
+};
+
// Represents a single event that has been configured in the Linux perf
// subsystem.
class ConfiguredEvent {
diff --git a/llvm/tools/llvm-exegesis/lib/Target.cpp b/llvm/tools/llvm-exegesis/lib/Target.cpp
index 9dc58dafbef7a..131810997ebb3 100644
--- a/llvm/tools/llvm-exegesis/lib/Target.cpp
+++ b/llvm/tools/llvm-exegesis/lib/Target.cpp
@@ -51,14 +51,10 @@ ExegesisTarget::getIgnoredOpcodeReasonOrNull(const LLVMState &State,
}
Expected<std::unique_ptr<pfm::CounterGroup>>
-ExegesisTarget::createCounter(StringRef CounterName, const LLVMState &,
+ExegesisTarget::createCounter(StringRef CounterName, const LLVMState &State,
ArrayRef<const char *> ValidationCounters,
const pid_t ProcessID) const {
- pfm::PerfEvent Event(CounterName);
- if (!Event.valid())
- return make_error<Failure>(Twine("Unable to create counter with name '")
- .concat(CounterName)
- .concat("'"));
+ const PfmCountersInfo &PCI = State.getPfmCounters();
std::vector<pfm::PerfEvent> ValidationEvents;
for (const char *ValCounterName : ValidationCounters) {
@@ -70,6 +66,38 @@ ExegesisTarget::createCounter(StringRef CounterName, const LLVMState &,
.concat("'"));
}
+ if (PCI.CycleCounterEventSelect != -1 &&
+ CounterName == StringRef(PCI.CycleCounter)) {
+ pfm::RawPerfEvent Event(PCI.CycleCounterEventSelect, PCI.CycleCounterUMask);
+ if (!Event.valid())
+ return make_error<Failure>(
+ Twine("Unable to create raw counter with EventSelect: ")
+ .concat(Twine(PCI.CycleCounterEventSelect))
+ .concat(" UMask: ")
+ .concat(Twine(PCI.CycleCounterUMask)));
+ return std::make_unique<pfm::CounterGroup>(
+ std::move(Event), std::move(ValidationEvents), ProcessID);
+ }
+
+ if (PCI.UopsCounterEventSelect != -1 &&
+ CounterName == StringRef(PCI.UopsCounter)) {
+ pfm::RawPerfEvent Event(PCI.UopsCounterEventSelect, PCI.UopsCounterUMask);
+ if (!Event.valid())
+ return make_error<Failure>(
+ Twine("Unable to create raw counter with EventSelect: ")
+ .concat(Twine(PCI.UopsCounterEventSelect))
+ .concat(" UMask: ")
+ .concat(Twine(PCI.UopsCounterUMask)));
+ return std::make_unique<pfm::CounterGroup>(
+ std::move(Event), std::move(ValidationEvents), ProcessID);
+ }
+
+ pfm::PerfEvent Event(CounterName);
+ if (!Event.valid())
+ return make_error<Failure>(Twine("Unable to create counter with name '")
+ .concat(CounterName)
+ .concat("'"));
+
return std::make_unique<pfm::CounterGroup>(
std::move(Event), std::move(ValidationEvents), ProcessID);
}
@@ -114,7 +142,8 @@ ExegesisTarget::createBenchmarkRunner(
case Benchmark::Latency:
case Benchmark::InverseThroughput:
if (BenchmarkPhaseSelector == BenchmarkPhaseSelectorE::Measure &&
- !PfmCounters.CycleCounter) {
+ !PfmCounters.CycleCounter &&
+ PfmCounters.CycleCounterEventSelect == -1) {
const char *ModeName = Mode == Benchmark::Latency
? "latency"
: "inverse_throughput";
@@ -132,7 +161,8 @@ ExegesisTarget::createBenchmarkRunner(
ValidationCounters, BenchmarkRepeatCount);
case Benchmark::Uops:
if (BenchmarkPhaseSelector == BenchmarkPhaseSelectorE::Measure &&
- !PfmCounters.UopsCounter && !PfmCounters.IssueCounters)
+ !PfmCounters.UopsCounter && !PfmCounters.IssueCounters &&
+ PfmCounters.UopsCounterEventSelect == -1)
return make_error<Failure>(
"can't run 'uops' mode, sched model does not define uops or issue "
"counters. You can pass --benchmark-phase=... to skip the actual "
diff --git a/llvm/unittests/tools/llvm-exegesis/CMakeLists.txt b/llvm/unittests/tools/llvm-exegesis/CMakeLists.txt
index 735f17ab03e61..387bec09482a8 100644
--- a/llvm/unittests/tools/llvm-exegesis/CMakeLists.txt
+++ b/llvm/unittests/tools/llvm-exegesis/CMakeLists.txt
@@ -18,6 +18,7 @@ set(exegesis_sources
ProgressMeterTest.cpp
RegisterValueTest.cpp
ResultAggregatorTest.cpp
+ PerfHelperTest.cpp
)
set(exegesis_link_libraries LLVMExegesis)
diff --git a/llvm/unittests/tools/llvm-exegesis/PerfHelperTest.cpp b/llvm/unittests/tools/llvm-exegesis/PerfHelperTest.cpp
new file mode 100644
index 0000000000000..35a78c7f0bc62
--- /dev/null
+++ b/llvm/unittests/tools/llvm-exegesis/PerfHelperTest.cpp
@@ -0,0 +1,60 @@
+//===-- PerfHelperTest.cpp --------------------------------*- C++ -*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "PerfHelper.h"
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+#ifdef HAVE_LIBPFM
+#include <perfmon/perf_event.h>
+#endif
+
+namespace llvm {
+namespace exegesis {
+
+namespace {
+
+#ifdef HAVE_LIBPFM
+TEST(RawPerfEventTest, ValidConstruction) {
+ pfm::RawPerfEvent Event1(0x003c, 0x00);
+ EXPECT_TRUE(Event1.valid());
+ EXPECT_NE(Event1.attribute(), nullptr);
+ EXPECT_EQ(Event1.name(), "raw:60:0");
+
+ pfm::RawPerfEvent Event2(0x00c2, 0x01);
+ EXPECT_TRUE(Event2.valid());
+ EXPECT_NE(Event2.attribute(), nullptr);
+ EXPECT_EQ(Event2.name(), "raw:194:1");
+
+ pfm::RawPerfEvent Event3(0x0000, 0x00);
+ EXPECT_TRUE(Event3.valid());
+ EXPECT_NE(Event3.attribute(), nullptr);
+ EXPECT_EQ(Event3.name(), "raw:0:0");
+}
+
+TEST(RawPerfEventTest, ConfigEncoding) {
+ pfm::RawPerfEvent Event1(0x003c, 0x01);
+ ASSERT_NE(Event1.attribute(), nullptr);
+ EXPECT_EQ(Event1.attribute()->config, (0x01 << 8) | 0x003cULL);
+ EXPECT_EQ(Event1.attribute()->type, PERF_TYPE_RAW);
+
+ pfm::RawPerfEvent Event2(0x00c2, 0x01);
+ ASSERT_NE(Event2.attribute(), nullptr);
+ EXPECT_EQ(Event2.attribute()->config, (0x01 << 8) | 0x00c2ULL);
+}
+
+TEST(RawPerfEventTest, ExcludeFlags) {
+ pfm::RawPerfEvent Event(0x003c, 0x00);
+ ASSERT_NE(Event.attribute(), nullptr);
+ EXPECT_EQ(Event.attribute()->exclude_kernel, 1u);
+ EXPECT_EQ(Event.attribute()->exclude_hv, 1u);
+}
+#endif
+
+} // namespace
+} // namespace exegesis
+} // namespace llvm
More information about the llvm-commits
mailing list