[llvm] 8a0406d - [llvm-profgen] Filter out invalid LBR ranges.

Hongtao Yu via llvm-commits llvm-commits at lists.llvm.org
Thu Apr 7 21:42:09 PDT 2022


Author: Hongtao Yu
Date: 2022-04-07T21:42:01-07:00
New Revision: 8a0406dcc8ec601e5612638a9d5ca049637c9366

URL: https://github.com/llvm/llvm-project/commit/8a0406dcc8ec601e5612638a9d5ca049637c9366
DIFF: https://github.com/llvm/llvm-project/commit/8a0406dcc8ec601e5612638a9d5ca049637c9366.diff

LOG: [llvm-profgen] Filter out invalid LBR ranges.

The profiler can sometimes give us a LBR trace that implicates bogus code ranges. For example,

    0xc5acb56/0xc66c6c0 0xc628195/0xf31fbb0 0xc611261/0xc628130 0xc5c1a21/0xc6111c0 0x1f7edfd3/0xc5c3a50 0xc5c154f/0x1f7edec0 0xe8eed07/0xc5c11e0

, note that the first two pairs are supposed to form a linear execution range, in this case, it is [0xf31fbb0, 0xc5acb56] , which doesn't make sense.

Such bogus ranges should be ruled out to avoid generating a bad profile. I'm fixing this for both CS and non-CS cases.

Reviewed By: wenlei

Differential Revision: https://reviews.llvm.org/D123271

Added: 
    llvm/test/tools/llvm-profgen/Inputs/invalid-range.perfscript
    llvm/test/tools/llvm-profgen/invalid-range.test

Modified: 
    llvm/tools/llvm-profgen/PerfReader.cpp
    llvm/tools/llvm-profgen/PerfReader.h

Removed: 
    


################################################################################
diff  --git a/llvm/test/tools/llvm-profgen/Inputs/invalid-range.perfscript b/llvm/test/tools/llvm-profgen/Inputs/invalid-range.perfscript
new file mode 100644
index 0000000000000..d6f855301b7ae
--- /dev/null
+++ b/llvm/test/tools/llvm-profgen/Inputs/invalid-range.perfscript
@@ -0,0 +1,10 @@
+ PERF_RECORD_MMAP2 1243676/1243676: [0x201000(0x1000) @ 0 00:1d 224517108 1044165]: r-xp /home/noinline-cs-pseudoprobe.perfbin
+
+	          20179e
+	          2017f9
+	    7f83e84e7793
+	5541f689495641d7
+ 0x2017cf/0x20179e/P/-/-/0  0x20177f/0x2017c4/P/-/-/0  0x2017bf/0x201760/P/-/-/0  0x2017d8/0x2017e3/P/-/-/0  0x20177f/0x2017c4/P/-/-/0  0x2017bf/0x201760/P/-/-/0  0x2017cf/0x20179e/P/-/-/0
+
+
+// The consecutive pairs 0x2017bf/0x201760 and 0x2017d8/0x2017e3 form an invalid execution range [0x2017e3, 0x2017bf], should be ignored to avoid bogus instruction ranges.

diff  --git a/llvm/test/tools/llvm-profgen/invalid-range.test b/llvm/test/tools/llvm-profgen/invalid-range.test
new file mode 100644
index 0000000000000..6aa0ef1df941a
--- /dev/null
+++ b/llvm/test/tools/llvm-profgen/invalid-range.test
@@ -0,0 +1,68 @@
+; In the perfscript input, the consecutive branch pairs 0x2017bf/0x201760 and 0x2017d8/0x2017e3 form an invalid execution range [0x2017e3, 0x2017bf].
+; We are testing only the invalid range is dropped to avoid bogus instruction ranges. All other ranges and all branch samples should be kept.
+;
+; RUN: llvm-profgen --format=text --perfscript=%S/Inputs/invalid-range.perfscript --binary=%S/Inputs/noinline-cs-pseudoprobe.perfbin --output=%t1 --skip-symbolization --ignore-stack-samples --use-offset=0
+; RUN: FileCheck %s --input-file %t1 --check-prefix=NOCS
+
+; RUN: llvm-profgen --format=text --perfscript=%S/Inputs/invalid-range.perfscript --binary=%S/Inputs/noinline-cs-pseudoprobe.perfbin --output=%t2 --skip-symbolization --use-offset=0
+; RUN: FileCheck %s --input-file %t2 --check-prefix=CS
+
+
+; NOCS:      4
+; NOCS-NEXT: 201760-20177f:2
+; NOCS-NEXT: 20179e-2017bf:1
+; NOCS-NEXT: 2017c4-2017cf:1
+; NOCS-NEXT: 2017c4-2017d8:1
+; NOCS-NEXT: 4
+; NOCS-NEXT: 20177f->2017c4:2
+; NOCS-NEXT: 2017bf->201760:2
+; NOCS-NEXT: 2017cf->20179e:2
+; NOCS-NEXT: 2017d8->2017e3:1
+
+
+; CS:      []
+; CS-NEXT:  3
+; CS-NEXT:  201760-20177f:1
+; CS-NEXT:  20179e-2017bf:1
+; CS-NEXT:  2017c4-2017d8:1
+; CS-NEXT:  4
+; CS-NEXT:  20177f->2017c4:1
+; CS-NEXT:  2017bf->201760:1
+; CS-NEXT:  2017cf->20179e:1
+; CS-NEXT:  2017d8->2017e3:1
+; CS-NEXT: [0x7f4]
+; CS-NEXT:  1
+; CS-NEXT:  2017c4-2017cf:1
+; CS-NEXT:  2
+; CS-NEXT:  2017bf->201760:1
+; CS-NEXT:  2017cf->20179e:1
+; CS-NEXT: [0x7f4 @ 0x7bf]
+; CS-NEXT:  1
+; CS-NEXT:  201760-20177f:1
+; CS-NEXT:  1
+; CS-NEXT:  20177f->2017c4:1
+
+; clang -O3 -fexperimental-new-pass-manager -fuse-ld=lld -fpseudo-probe-for-profiling
+; -fno-omit-frame-pointer -mno-omit-leaf-frame-pointer -Xclang -mdisable-tail-calls
+; -g test.c  -o a.out
+
+#include <stdio.h>
+
+int bar(int x, int y) {
+  if (x % 3) {
+    return x - y;
+  }
+  return x + y;
+}
+
+void foo() {
+  int s, i = 0;
+  while (i++ < 4000 * 4000)
+    if (i % 91) s = bar(i, s); else s += 30;
+  printf("sum is %d\n", s);
+}
+
+int main() {
+  foo();
+  return 0;
+}

diff  --git a/llvm/tools/llvm-profgen/PerfReader.cpp b/llvm/tools/llvm-profgen/PerfReader.cpp
index 02f653e68e899..ec4362c856747 100644
--- a/llvm/tools/llvm-profgen/PerfReader.cpp
+++ b/llvm/tools/llvm-profgen/PerfReader.cpp
@@ -100,6 +100,11 @@ void VirtualUnwinder::unwindLinear(UnwindState &State, uint64_t Repeat) {
   InstructionPointer &IP = State.InstPtr;
   uint64_t Target = State.getCurrentLBRTarget();
   uint64_t End = IP.Address;
+  if (Target > End) {
+    // Skip unwinding the rest of LBR trace when a bogus range is seen.
+    State.setInvalid();
+    return;
+  }
   if (Binary->usePseudoProbes()) {
     // We don't need to top frame probe since it should be extracted
     // from the range.
@@ -303,19 +308,28 @@ bool VirtualUnwinder::unwind(const PerfSample *Sample, uint64_t Repeat) {
       // extra frame when processing the return paired with this call.
       unwindCall(State);
     } else if (isReturnState(State)) {
-      // Unwind returns - check whether the IP is indeed at a return instruction
+      // Unwind returns - check whether the IP is indeed at a return
+      // instruction
       unwindReturn(State);
-    } else {
+    } else if (isValidState(State)) {
       // Unwind branches
-      // For regular intra function branches, we only need to record branch with
-      // context. For an artificial branch cross function boundaries, we got an
-      // issue with returning to external code. Take the two LBR enties for
-      // example: [foo:8(RETURN), ext:1] [ext:3(CALL), bar:1] After perf reader,
-      // we only get[foo:8(RETURN), bar:1], unwinder will be confused like foo
-      // return to bar. Here we detect and treat this case as BRANCH instead of
-      // RETURN which only update the source address.
+      // For regular intra function branches, we only need to record branch
+      // with context. For an artificial branch cross function boundaries, we
+      // got an issue with returning to external code. Take the two LBR enties
+      // for example: [foo:8(RETURN), ext:1] [ext:3(CALL), bar:1] After perf
+      // reader, we only get[foo:8(RETURN), bar:1], unwinder will be confused
+      // like foo return to bar. Here we detect and treat this case as BRANCH
+      // instead of RETURN which only update the source address.
       unwindBranch(State);
+    } else {
+      // Skip unwinding the rest of LBR trace. Reset the stack and update the
+      // state so that the rest of the trace can still be processed as if they
+      // do not have stack samples.
+      State.clearCallStack();
+      State.InstPtr.update(State.getCurrentLBRSource());
+      State.pushFrame(State.InstPtr.Address);
     }
+
     State.advanceLBR();
     // Record `branch` with calling context after unwinding.
     recordBranchCount(Branch, State, Repeat);
@@ -720,7 +734,9 @@ void HybridPerfReader::parseSample(TraceStream &TraceIt, uint64_t Count) {
   //          ... 0x4005c8/0x4005dc/P/-/-/0    # LBR Entries
   //
   std::shared_ptr<PerfSample> Sample = std::make_shared<PerfSample>();
-
+#ifndef NDEBUG
+  Sample->Linenum = TraceIt.getLineNumber();
+#endif
   // Parsing call stack and populate into PerfSample.CallStack
   if (!extractCallstack(TraceIt, Sample->CallStack)) {
     // Skip the next LBR line matched current call stack
@@ -915,8 +931,10 @@ void PerfScriptReader::computeCounterFromLBR(const PerfSample *Sample,
     // If this not the first LBR, update the range count between TO of current
     // LBR and FROM of next LBR.
     uint64_t StartOffset = TargetOffset;
-    if (EndOffeset != 0)
-      Counter.recordRangeCount(StartOffset, EndOffeset, Repeat);
+    if (EndOffeset != 0) {
+      if (StartOffset <= EndOffeset)
+        Counter.recordRangeCount(StartOffset, EndOffeset, Repeat);
+    }
     EndOffeset = SourceOffset;
   }
 }
@@ -1161,41 +1179,55 @@ void PerfScriptReader::warnInvalidRange() {
   const char *RangeCrossFuncMsg =
       "Fall through range should not cross function boundaries, likely due to "
       "profile and binary mismatch.";
+  const char *BogusRangeMsg = "Range start is after range end.";
 
+  uint64_t TotalRangeNum = 0;
   uint64_t InstNotBoundary = 0;
   uint64_t UnmatchedRange = 0;
   uint64_t RangeCrossFunc = 0;
+  uint64_t BogusRange = 0;
 
   for (auto &I : Ranges) {
     uint64_t StartOffset = I.first.first;
     uint64_t EndOffset = I.first.second;
+    TotalRangeNum += I.second;
 
     if (!Binary->offsetIsCode(StartOffset) ||
         !Binary->offsetIsTransfer(EndOffset)) {
-      InstNotBoundary++;
+      InstNotBoundary += I.second;
       WarnInvalidRange(StartOffset, EndOffset, EndNotBoundaryMsg);
     }
 
     auto *FRange = Binary->findFuncRangeForOffset(StartOffset);
     if (!FRange) {
-      UnmatchedRange++;
+      UnmatchedRange += I.second;
       WarnInvalidRange(StartOffset, EndOffset, DanglingRangeMsg);
       continue;
     }
 
     if (EndOffset >= FRange->EndOffset) {
-      RangeCrossFunc++;
+      RangeCrossFunc += I.second;
       WarnInvalidRange(StartOffset, EndOffset, RangeCrossFuncMsg);
     }
+
+    if (StartOffset > EndOffset) {
+      BogusRange += I.second;
+      WarnInvalidRange(StartOffset, EndOffset, BogusRangeMsg);
+    }
   }
 
-  uint64_t TotalRangeNum = Ranges.size();
-  emitWarningSummary(InstNotBoundary, TotalRangeNum,
-                     "of profiled ranges are not on instruction boundary.");
-  emitWarningSummary(UnmatchedRange, TotalRangeNum,
-                     "of profiled ranges do not belong to any functions.");
-  emitWarningSummary(RangeCrossFunc, TotalRangeNum,
-                     "of profiled ranges do cross function boundaries.");
+  emitWarningSummary(
+      InstNotBoundary, TotalRangeNum,
+      "of samples are from ranges that are not on instruction boundary.");
+  emitWarningSummary(
+      UnmatchedRange, TotalRangeNum,
+      "of samples are from ranges that do not belong to any functions.");
+  emitWarningSummary(
+      RangeCrossFunc, TotalRangeNum,
+      "of samples are from ranges that do cross function boundaries.");
+  emitWarningSummary(
+      BogusRange, TotalRangeNum,
+      "of samples are from ranges that have range start after range end.");
 }
 
 void PerfScriptReader::parsePerfTraces() {

diff  --git a/llvm/tools/llvm-profgen/PerfReader.h b/llvm/tools/llvm-profgen/PerfReader.h
index 021fb06fcbf43..052d1b4b3e8fa 100644
--- a/llvm/tools/llvm-profgen/PerfReader.h
+++ b/llvm/tools/llvm-profgen/PerfReader.h
@@ -197,7 +197,10 @@ struct PerfSample {
   }
 
 #ifndef NDEBUG
+  uint64_t Linenum = 0;
+
   void print() const {
+    dbgs() << "Line " << Linenum << "\n";
     dbgs() << "LBR stack\n";
     printLBRStack(LBRStack);
     dbgs() << "Call stack\n";
@@ -255,6 +258,9 @@ struct UnwindState {
   const SmallVector<LBREntry, 16> &LBRStack;
   // Used to iterate the address range
   InstructionPointer InstPtr;
+  // Indicate whether unwinding is currently in a bad state which requires to
+  // skip all subsequent unwinding.
+  bool Invalid = false;
   UnwindState(const PerfSample *Sample, const ProfiledBinary *Binary)
       : Binary(Binary), LBRStack(Sample->LBRStack),
         InstPtr(Binary, Sample->CallStack.front()) {
@@ -284,6 +290,7 @@ struct UnwindState {
            "IP should align with context leaf");
   }
 
+  void setInvalid() { Invalid = true; }
   bool hasNextLBR() const { return LBRIndex < LBRStack.size(); }
   uint64_t getCurrentLBRSource() const { return LBRStack[LBRIndex].Source; }
   uint64_t getCurrentLBRTarget() const { return LBRStack[LBRIndex].Target; }
@@ -476,10 +483,14 @@ class VirtualUnwinder {
   bool isCallState(UnwindState &State) const {
     // The tail call frame is always missing here in stack sample, we will
     // use a specific tail call tracker to infer it.
-    return Binary->addressIsCall(State.getCurrentLBRSource());
+    return isValidState(State) &&
+           Binary->addressIsCall(State.getCurrentLBRSource());
   }
 
   bool isReturnState(UnwindState &State) const {
+    if (!isValidState(State))
+      return false;
+
     // Simply check addressIsReturn, as ret is always reliable, both for
     // regular call and tail call.
     if (!Binary->addressIsReturn(State.getCurrentLBRSource()))
@@ -497,6 +508,8 @@ class VirtualUnwinder {
     return (CallAddr != 0);
   }
 
+  bool isValidState(UnwindState &State) const { return !State.Invalid; }
+
   void unwindCall(UnwindState &State);
   void unwindLinear(UnwindState &State, uint64_t Repeat);
   void unwindReturn(UnwindState &State);


        


More information about the llvm-commits mailing list