[llvm] 28fec94 - [llvm-profgen] Fix bogus trace check (#225569)
via llvm-commits
llvm-commits at lists.llvm.org
Wed Sep 23 18:09:19 PDT 2026
Author: Amir Ayupov
Date: 2026-09-23T18:09:10-07:00
New Revision: 28fec94991905d8dd8a08658e9590e13b7fe1af5
URL: https://github.com/llvm/llvm-project/commit/28fec94991905d8dd8a08658e9590e13b7fe1af5
DIFF: https://github.com/llvm/llvm-project/commit/28fec94991905d8dd8a08658e9590e13b7fe1af5.diff
LOG: [llvm-profgen] Fix bogus trace check (#225569)
Added:
llvm/test/tools/llvm-profgen/AArch64/cs-bogus-trace.test
Modified:
llvm/test/tools/llvm-profgen/lit.local.cfg
llvm/tools/llvm-profgen/PerfReader.cpp
llvm/tools/llvm-profgen/PerfReader.h
Removed:
################################################################################
diff --git a/llvm/test/tools/llvm-profgen/AArch64/cs-bogus-trace.test b/llvm/test/tools/llvm-profgen/AArch64/cs-bogus-trace.test
new file mode 100644
index 0000000000000..2b2eb64a861f5
--- /dev/null
+++ b/llvm/test/tools/llvm-profgen/AArch64/cs-bogus-trace.test
@@ -0,0 +1,160 @@
+# REQUIRES: lld
+# RUN: split-file %s %t
+# RUN: llvm-mc -triple=aarch64 -filetype=obj %t/a.s -o %t/a.o
+# RUN: ld.lld --image-base=0 -Ttext=0x1000 -e main %t/a.o -o %t/cs-bogus-trace.exe
+# RUN: llvm-profgen --binary=%t/cs-bogus-trace.exe --perfscript=%t/perfscript \
+# RUN: --skip-symbolization --format=text --output=%t/profile \
+# RUN: --show-detailed-warning 2>&1 \
+# RUN: | FileCheck %s --implicit-check-not="Bogus trace"
+# RUN: llvm-profgen --binary=%t/cs-bogus-trace.exe --perfscript=%t/perfscript-clean \
+# RUN: --skip-symbolization --format=text --output=%t/profile-clean \
+# RUN: --show-detailed-warning 2>&1 \
+# RUN: | FileCheck %s --check-prefix=CLEAN --allow-empty
+
+## A hybrid sample is kept only if the callchain leaf (sampled pc) and the
+## newest LBR target lie in the same function, or either is external. The leaf
+## is then canonicalized to the LBR target, so skid within one function is
+## harmless.
+
+## Cross-function skid is rejected.
+# CHECK: Bogus trace: stack tip = 0x00001088, LBR tip = 0x00001000
+# CHECK: Bogus trace: stack tip = 0x00001008, LBR tip = 0x00001094
+# CHECK: Bogus trace: stack tip = 0x00001088, LBR tip = 0x00001060
+# CHECK: 33.33%(3/9) of hybrid samples had a callchain leaf that disagreed with the newest LBR target (bogus trace).
+# CHECK: Bogus trace rate exceeds 1%: the profile has high sample skid and may not be suitable for optimization.
+
+## No warnings when every sample is kept.
+# CLEAN-NOT: Bogus trace
+
+#--- a.s
+.macro func name
+ .globl \name
+ .type \name,%function
+\name:
+.endm
+
+.macro endfunc name
+ .size \name, .-\name
+.endm
+
+ .text
+func g // 0x1000
+ nop
+ nop
+ nop
+ nop
+ nop
+ nop
+ nop
+ ret // 0x101c
+endfunc g
+
+func r // 0x1020
+ nop
+ nop
+ nop
+ bl r // 0x102c: r -> r
+ nop
+ nop
+ nop
+ ret
+endfunc r
+
+func main // 0x1040
+ nop
+ nop
+ bl f // 0x1048: main -> f
+ nop
+ bl r // 0x1050: main -> r
+ nop
+ nop
+ ret
+endfunc main
+
+func h // 0x1060
+ nop
+ nop
+ nop
+ nop
+ nop
+ nop
+ nop
+ ret
+endfunc h
+
+func f // 0x1080
+ nop
+ nop
+ nop
+ nop
+ bl g // 0x1090: f -> g
+ nop
+ nop
+ b h // 0x109c: f -> h (tail call)
+ .space 0x160 // make f larger than 256 bytes
+endfunc f
+
+// profgen takes function ranges from symbol sizes only in pseudo-probe mode.
+ .section .pseudo_probe_desc,"",%progbits
+ .section .pseudo_probe,"",%progbits
+
+#--- perfscript
+PERF_RECORD_MMAP2 1/1: [0x0(0x2000) @ 0 00:00 0 0]: r-xp /tmp/cs-bogus-trace.exe
+
+; Small positive skid: pc 8 bytes past the LBR target in f. Kept.
+ 1088
+ 104c
+ 0x1048/0x1080/P/-/-/0
+
+; Positive skid of 260 bytes within f, beyond the old 0x100 cap. Kept.
+ 1184
+ 104c
+ 0x1048/0x1080/P/-/-/0
+
+; Negative skid within f: the LBR is newer than the pc (g returned to f).
+; The frames above the leaf are unchanged. Kept.
+ 1088
+ 104c
+ 0x101c/0x1094/P/-/-/0 0x1090/0x1000/P/-/-/0 0x1048/0x1080/P/-/-/0
+
+; Skid across a call: pc in f, newest branch is f -> g. Dropped.
+ 1088
+ 104c
+ 0x1090/0x1000/P/-/-/0 0x1048/0x1080/P/-/-/0
+
+; Skid across a return: pc in g, newest branch is g returning to f. Dropped.
+ 1008
+ 1094
+ 104c
+ 0x101c/0x1094/P/-/-/0 0x1090/0x1000/P/-/-/0 0x1048/0x1080/P/-/-/0
+
+; Skid across a tail call: pc in f, newest branch is f -> h. Dropped.
+ 1088
+ 104c
+ 0x109c/0x1060/P/-/-/0 0x1048/0x1080/P/-/-/0
+
+; Skid across a recursive call: pc in the inner r, but the LBR is older and
+; its newest branch is main -> outer r. Both are in r, so this is not caught.
+ 1028
+ 1030
+ 1054
+ 0x1050/0x1020/P/-/-/0
+
+; External leaf: pc in a library called from f. Not checked. Kept.
+ 7f0000001000
+ 1094
+ 104c
+ 0x1048/0x1080/P/-/-/0
+
+; External LBR target: newest branch leaves the binary. Not checked. Kept.
+ 1088
+ 104c
+ 0x1090/0x7f0000001000/P/-/-/0 0x1048/0x1080/P/-/-/0
+
+#--- perfscript-clean
+PERF_RECORD_MMAP2 1/1: [0x0(0x2000) @ 0 00:00 0 0]: r-xp /tmp/cs-bogus-trace.exe
+
+; Small positive skid within f. Kept.
+ 1088
+ 104c
+ 0x1048/0x1080/P/-/-/0
diff --git a/llvm/test/tools/llvm-profgen/lit.local.cfg b/llvm/test/tools/llvm-profgen/lit.local.cfg
index 860e1e9557a7f..a0316aa239b40 100644
--- a/llvm/test/tools/llvm-profgen/lit.local.cfg
+++ b/llvm/test/tools/llvm-profgen/lit.local.cfg
@@ -1,4 +1,9 @@
+from lit.llvm import llvm_config
+
config.suffixes = [".test", ".ll", ".s", ".yaml"]
# Allow tests to prepend a mock directory onto the real PATH.
config.substitutions.append(("%{PATH}", config.environment["PATH"]))
+
+if llvm_config.use_lld(required=False):
+ config.available_features.add("lld")
diff --git a/llvm/tools/llvm-profgen/PerfReader.cpp b/llvm/tools/llvm-profgen/PerfReader.cpp
index 18297d5f7665b..4c266b4f3d9aa 100644
--- a/llvm/tools/llvm-profgen/PerfReader.cpp
+++ b/llvm/tools/llvm-profgen/PerfReader.cpp
@@ -300,11 +300,6 @@ bool VirtualUnwinder::unwind(const PerfSample *Sample, uint64_t Repeat) {
// Capture initial state as starting point for unwinding.
UnwindState State(Sample, Binary);
- // Sanity check - making sure leaf of LBR aligns with leaf of stack sample
- // Stack sample sometimes can be unreliable, so filter out bogus ones.
- if (!State.validateInitialState())
- return false;
-
NumTotalBranches += State.LBRStack.size();
// Now process the LBR samples in parrallel with stack sample
// Note that we do not reverse the LBR entry order so we can
@@ -722,6 +717,14 @@ void HybridPerfReader::unwindSamples() {
Unwinder.NumExtCallBranch,
"of artificial call branches but doesn't have an external "
"frame to match.");
+
+ emitWarningSummary(NumBogusTrace, NumTotalHybridSample,
+ "of hybrid samples had a callchain leaf that disagreed "
+ "with the newest LBR target (bogus trace).");
+ if (NumBogusTrace * 100 > NumTotalHybridSample)
+ WithColor::warning() << "Bogus trace rate exceeds 1%: the profile has high "
+ "sample skid and may not be suitable for "
+ "optimization.\n";
}
/// Parse a hex address from \p Str.
@@ -881,6 +884,15 @@ void PerfScriptReader::warnIfMissingMMap() {
}
}
+// The unwinder requires that LBR tip belong to the leaf frame.
+// External addresses are not checked.
+static bool isValidTrace(ProfiledBinary *Binary, uint64_t StackLeaf,
+ uint64_t LBRLeaf) {
+ if (StackLeaf == ExternalAddr || LBRLeaf == ExternalAddr)
+ return true;
+ return Binary->findFuncRange(LBRLeaf) == Binary->findFuncRange(StackLeaf);
+}
+
void HybridPerfReader::parseSample(TraceStream &TraceIt, uint64_t Count) {
// The raw hybird sample started with call stack in FILO order and followed
// intermediately by LBR sample
@@ -911,6 +923,19 @@ void HybridPerfReader::parseSample(TraceStream &TraceIt, uint64_t Count) {
if (IgnoreStackSamples) {
Sample->CallStack.clear();
} else {
+ NumTotalHybridSample++;
+ // Drop samples whose callchain and LBR disagree before the
+ // canonicalization below hides the disagreement.
+ uint64_t StackLeaf = Sample->CallStack.front();
+ uint64_t LBRLeaf = Sample->LBRStack[0].Target;
+ if (!isValidTrace(Binary, StackLeaf, LBRLeaf)) {
+ NumBogusTrace++;
+ if (ShowDetailedWarning)
+ WithColor::warning()
+ << "Bogus trace: stack tip = " << format("%#010x", StackLeaf)
+ << ", LBR tip = " << format("%#010x\n", LBRLeaf);
+ return;
+ }
// Canonicalize stack leaf to avoid 'random' IP from leaf frame skew LBR
// ranges
Sample->CallStack.front() = Sample->LBRStack[0].Target;
diff --git a/llvm/tools/llvm-profgen/PerfReader.h b/llvm/tools/llvm-profgen/PerfReader.h
index b9af0f19cb5d3..b6e47b7c71d9f 100644
--- a/llvm/tools/llvm-profgen/PerfReader.h
+++ b/llvm/tools/llvm-profgen/PerfReader.h
@@ -272,24 +272,6 @@ struct UnwindState {
initFrameTrie(Sample->CallStack);
}
- bool validateInitialState() {
- uint64_t LBRLeaf = LBRStack[LBRIndex].Target;
- uint64_t LeafAddr = CurrentLeafFrame->Address;
- assert((LBRLeaf != ExternalAddr || LBRLeaf == LeafAddr) &&
- "External leading LBR should match the leaf frame.");
-
- // When we take a stack sample, ideally the sampling distance between the
- // leaf IP of stack and the last LBR target shouldn't be very large.
- // Use a heuristic size (0x100) to filter out broken records.
- if (LeafAddr < LBRLeaf || LeafAddr - LBRLeaf >= 0x100) {
- WithColor::warning() << "Bogus trace: stack tip = "
- << format("%#010x", LeafAddr)
- << ", LBR tip = " << format("%#010x\n", LBRLeaf);
- return false;
- }
- return true;
- }
-
void checkStateConsistency() {
assert(InstPtr.Address == CurrentLeafFrame->Address &&
"IP should align with context leaf");
@@ -716,6 +698,9 @@ class HybridPerfReader : public PerfScriptReader {
private:
// Unwind the hybrid samples after aggregration
void unwindSamples();
+
+ uint64_t NumBogusTrace = 0;
+ uint64_t NumTotalHybridSample = 0;
};
/*
More information about the llvm-commits
mailing list