[llvm] [llvm-exegesis] Fix ~27 minute stall when no LBR sample arrives (PR #218102)
Fangrui Song via llvm-commits
llvm-commits at lists.llvm.org
Fri Aug 21 22:51:25 PDT 2026
https://github.com/MaskRay created https://github.com/llvm/llvm-project/pull/218102
X86LbrPerfEvent programs PERF_TYPE_RAW/0x20c4, a P-core encoding. A hybrid
CPU registers one PMU per core type and cpu_core claims PERF_TYPE_RAW, so
the kernel never schedules the event while the thread runs on an E-core.
perf_event_open succeeds all the same and no sample is recorded.
doReadCounter polls 160 times with a 10s timeout, so reporting the failure
takes ~27 minutes, and checkLbrSupport then aborts instead of reporting it,
never having consumed the Expected error. lit.local.cfg probes llvm-exegesis
at test discovery, so with LLVM_ENABLE_LIBPFM=ON this stalls all of
check-llvm before any test runs.
The event is disabled before we poll, so a sample either is already in the
ring buffer -- wakeup_events == 1 leaves POLLIN asserted -- or it will never
arrive. Poll 3 times with a 1s timeout, use expectedToOptional, and bound
the lit probe. A probe that works at all takes under 30ms, 0.08s with every
core saturated.
Scheduling the event on a core that can count it is a separate matter.
LLM-aided
>From 7887d029e73b2457371b791f40b365a1b0113263 Mon Sep 17 00:00:00 2001
From: Fangrui Song <i at maskray.me>
Date: Fri, 21 Aug 2026 22:41:20 -0700
Subject: [PATCH] [llvm-exegesis] Fix ~27 minute stall when no LBR sample
arrives
X86LbrPerfEvent programs PERF_TYPE_RAW/0x20c4, a P-core encoding. A hybrid
CPU registers one PMU per core type and cpu_core claims PERF_TYPE_RAW, so
the kernel never schedules the event while the thread runs on an E-core.
perf_event_open succeeds all the same and no sample is recorded.
doReadCounter polls 160 times with a 10s timeout, so reporting the failure
takes ~27 minutes, and checkLbrSupport then aborts instead of reporting it,
never having consumed the Expected error. lit.local.cfg probes llvm-exegesis
at test discovery, so with LLVM_ENABLE_LIBPFM=ON this stalls all of
check-llvm before any test runs.
The event is disabled before we poll, so a sample either is already in the
ring buffer -- wakeup_events == 1 leaves POLLIN asserted -- or it will never
arrive. Poll 3 times with a 1s timeout, use expectedToOptional, and bound
the lit probe. A probe that works at all takes under 30ms, 0.08s with every
core saturated.
Scheduling the event on a core that can count it is a separate matter.
LLM-aided
---
llvm/test/tools/llvm-exegesis/lit.local.cfg | 7 +++
.../llvm-exegesis/lib/X86/X86Counter.cpp | 47 +++++++++----------
2 files changed, 30 insertions(+), 24 deletions(-)
diff --git a/llvm/test/tools/llvm-exegesis/lit.local.cfg b/llvm/test/tools/llvm-exegesis/lit.local.cfg
index 89110ed2816cd..e6b2ceb187fc1 100644
--- a/llvm/test/tools/llvm-exegesis/lit.local.cfg
+++ b/llvm/test/tools/llvm-exegesis/lit.local.cfg
@@ -19,13 +19,20 @@ def can_use_perf_counters(mode, extra_options=[]):
print("could not find llvm-exegesis")
return False
try:
+ # This runs during test discovery, so a hung llvm-exegesis would stall the
+ # whole lit invocation before a single test starts. A probe takes well
+ # under a second when it works at all.
return_code = subprocess.call(
[llvm_exegesis_exe, "-mode", mode, "-opcode-name=ADD64rr"]
+ extra_options,
stdout=subprocess.DEVNULL,
stderr=subprocess.DEVNULL,
+ timeout=1,
)
return return_code == 0
+ except subprocess.TimeoutExpired:
+ print("llvm-exegesis timed out")
+ return False
except OSError:
print("could not exec llvm-exegesis")
return False
diff --git a/llvm/tools/llvm-exegesis/lib/X86/X86Counter.cpp b/llvm/tools/llvm-exegesis/lib/X86/X86Counter.cpp
index 9dc6c764599cd..6a5120bc56636 100644
--- a/llvm/tools/llvm-exegesis/lib/X86/X86Counter.cpp
+++ b/llvm/tools/llvm-exegesis/lib/X86/X86Counter.cpp
@@ -14,6 +14,7 @@
// FIXME: Use appropriate wrappers for poll.h and mman.h
// to support Windows and remove this linux-only guard.
+#include "llvm/ADT/STLExtras.h"
#include "llvm/Support/Endian.h"
#include "llvm/Support/Errc.h"
@@ -44,13 +45,16 @@ static const size_t kDataBufferSize = kBufferPages * getpagesize();
// the next page, so we allocate one more page.
static const size_t kMappedBufferSize = (kBufferPages + 1) * getpagesize();
+static constexpr int kPollTimeoutMs = 1000;
+static constexpr int kMaxPolls = 3;
+
// Waits for the LBR perf events.
static int pollLbrPerfEvent(const int FileDescriptor) {
struct pollfd PollFd;
PollFd.fd = FileDescriptor;
PollFd.events = POLLIN;
PollFd.revents = 0;
- return poll(&PollFd, 1 /* num of fds */, 10000 /* timeout in ms */);
+ return poll(&PollFd, 1 /* num of fds */, kPollTimeoutMs);
}
// Copies the data-buffer into Buf, given the pointer to MMapped.
@@ -178,13 +182,11 @@ Error X86LbrCounter::checkLbrSupport() {
counter.stop();
(void)Sum;
- auto ResultOrError = counter.doReadCounter(nullptr, nullptr);
- if (ResultOrError)
- if (!ResultOrError.get().empty())
- // If there is at least one non-zero entry, then LBR is supported.
- for (const int64_t &Value : ResultOrError.get())
- if (Value != 0)
- return Error::success();
+ // A read that fails just means LBR is unusable here. If there is at least one
+ // non-zero entry, then LBR is supported.
+ if (auto Result = expectedToOptional(counter.doReadCounter(nullptr, nullptr)))
+ if (any_of(*Result, [](int64_t Value) { return Value != 0; }))
+ return Error::success();
return make_error<StringError>(
"LBR format with cycles is not suppported on the host.",
@@ -209,28 +211,25 @@ X86LbrCounter::readOrError(StringRef FunctionBytes) const {
Expected<SmallVector<int64_t, 4>>
X86LbrCounter::doReadCounter(const void *From, const void *To) const {
- // The max number of time-outs/retries before we give up.
- static constexpr int kMaxTimeouts = 160;
-
// Parses the LBR buffer and fills CycleArray with the sequence of cycle
// counts from the buffer.
SmallVector<int64_t, 4> CycleArray;
auto DataBuf = std::make_unique<char[]>(kDataBufferSize);
- int NumTimeouts = 0;
- int PollResult = 0;
- while (PollResult <= 0) {
+ // The event is disabled before we get here, so a sample either is already in
+ // the ring buffer -- wakeup_events == 1 leaves POLLIN asserted -- or it will
+ // never arrive. The budget only absorbs wake-up latency.
+ int PollResult = 0;
+ for (int I = 0; I != kMaxPolls && PollResult == 0; ++I)
PollResult = pollLbrPerfEvent(getFileDescriptor());
- if (PollResult > 0)
- break;
- if (PollResult == -1)
- return make_error<StringError>("Cannot poll LBR perf event.",
- errc::io_error);
- if (NumTimeouts++ >= kMaxTimeouts)
- return make_error<StringError>(
- "LBR polling still timed out after max number of attempts.",
- errc::device_or_resource_busy);
- }
+
+ if (PollResult < 0)
+ return make_error<StringError>("Cannot poll LBR perf event.",
+ errc::io_error);
+ if (PollResult == 0)
+ return make_error<StringError>(
+ "LBR polling still timed out after max number of attempts.",
+ errc::device_or_resource_busy);
struct perf_event_mmap_page Page;
memcpy(&Page, MMappedBuffer, sizeof(struct perf_event_mmap_page));
More information about the llvm-commits
mailing list