[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