[llvm-exegesis] Analysis: detect clustering inconsistencies.

Summary:
Warn on instructions that should have the same performance
characteristics according to the sched model but actually
differ in their benchmarks.

Next step: Make the display nicer to browse, I was thinking maybe html.

Reviewers: gchatelet

Subscribers: tschuett, llvm-commits

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

llvm-svn: 332601
diff --git a/llvm/tools/llvm-exegesis/lib/Analysis.cpp b/llvm/tools/llvm-exegesis/lib/Analysis.cpp
index e026308..5b60925 100644
--- a/llvm/tools/llvm-exegesis/lib/Analysis.cpp
+++ b/llvm/tools/llvm-exegesis/lib/Analysis.cpp
@@ -10,6 +10,7 @@
 #include "Analysis.h"
 #include "BenchmarkResult.h"
 #include "llvm/Support/FormatVariadic.h"
+#include <unordered_set>
 #include <vector>
 
 namespace exegesis {
@@ -34,26 +35,36 @@
 
 // Prints a row representing an instruction, along with scheduling info and
 // point coordinates (measurements).
-void Analysis::printInstructionRow(const size_t ClusterId, const size_t PointId,
+void Analysis::printInstructionRow(const bool PrintSchedClass,
+                                   const size_t PointId,
                                    llvm::raw_ostream &OS) const {
   const InstructionBenchmark &Point = Clustering_.getPoints()[PointId];
-
-  OS << ClusterId << kCsvSep;
+  const auto &ClusterId = Clustering_.getClusterIdForPoint(PointId);
+  if (ClusterId.isNoise())
+    OS << "[noise]";
+  else if (ClusterId.isError())
+    OS << "[error]";
+  else
+    OS << ClusterId.getId();
+  OS << kCsvSep;
   writeCsvEscaped(OS, Point.Key.OpcodeName);
   OS << kCsvSep;
   writeCsvEscaped(OS, Point.Key.Config);
-  OS << kCsvSep;
-  const auto OpcodeIt = MnemonicToOpcode_.find(Point.Key.OpcodeName);
-  if (OpcodeIt != MnemonicToOpcode_.end()) {
-    const unsigned SchedClassId = InstrInfo_->get(OpcodeIt->second).getSchedClass();
+  if (PrintSchedClass) {
+    OS << kCsvSep;
+    const auto OpcodeIt = MnemonicToOpcode_.find(Point.Key.OpcodeName);
+    if (OpcodeIt != MnemonicToOpcode_.end()) {
+      const unsigned SchedClassId =
+          InstrInfo_->get(OpcodeIt->second).getSchedClass();
 #if !defined(NDEBUG) || defined(LLVM_ENABLE_DUMP)
-    const auto &SchedModel = SubtargetInfo_->getSchedModel();
-    const llvm::MCSchedClassDesc *const SCDesc =
-       SchedModel.getSchedClassDesc(SchedClassId);
-    writeCsvEscaped(OS, SCDesc->Name);
+      const auto &SchedModel = SubtargetInfo_->getSchedModel();
+      const llvm::MCSchedClassDesc *const SCDesc =
+          SchedModel.getSchedClassDesc(SchedClassId);
+      writeCsvEscaped(OS, SCDesc->Name);
 #else
-    OS << SchedClassId;
+      OS << SchedClassId;
 #endif
+    }
   }
   // FIXME: Print the sched class once InstructionBenchmark separates key into
   // (mnemonic, mode, opaque).
@@ -72,8 +83,8 @@
 
   InstrInfo_.reset(Target.createMCInstrInfo());
   const InstructionBenchmark &FirstPoint = Clustering.getPoints().front();
-  SubtargetInfo_.reset(Target.createMCSubtargetInfo(
-      FirstPoint.LLVMTriple, FirstPoint.CpuName, ""));
+  SubtargetInfo_.reset(Target.createMCSubtargetInfo(FirstPoint.LLVMTriple,
+                                                    FirstPoint.CpuName, ""));
 
   // Build an index of mnemonic->opcode.
   for (int I = 0, E = InstrInfo_->getNumOpcodes(); I < E; ++I)
@@ -94,14 +105,66 @@
   OS << "\n";
 
   // Write the points.
-  const auto& Clusters = Clustering_.getValidClusters();
+  const auto &Clusters = Clustering_.getValidClusters();
   for (size_t I = 0, E = Clusters.size(); I < E; ++I) {
     for (const size_t PointId : Clusters[I].PointIndices) {
-      printInstructionRow(I, PointId, OS);
+      printInstructionRow(/*PrintSchedClass*/ true, PointId, OS);
     }
     OS << "\n\n";
   }
   return llvm::Error::success();
 }
 
+std::unordered_map<unsigned, std::vector<size_t>>
+Analysis::makePointsPerSchedClass() const {
+  std::unordered_map<unsigned, std::vector<size_t>> PointsPerSchedClass;
+  const auto &Points = Clustering_.getPoints();
+  for (size_t PointId = 0, E = Points.size(); PointId < E; ++PointId) {
+    const InstructionBenchmark &Point = Points[PointId];
+    if (!Point.Error.empty())
+      continue;
+    const auto OpcodeIt = MnemonicToOpcode_.find(Point.Key.OpcodeName);
+    if (OpcodeIt == MnemonicToOpcode_.end())
+      continue;
+    const unsigned SchedClassId =
+        InstrInfo_->get(OpcodeIt->second).getSchedClass();
+    PointsPerSchedClass[SchedClassId].push_back(PointId);
+  }
+  return PointsPerSchedClass;
+}
+
+llvm::Error
+Analysis::printSchedClassInconsistencies(llvm::raw_ostream &OS) const {
+  // All the points in a scheduling class should be in the same cluster.
+  // Print any scheduling class for which this is not the case.
+  for (const auto &SchedClassAndPoints : makePointsPerSchedClass()) {
+    std::unordered_set<size_t> ClustersForSchedClass;
+    for (const size_t PointId : SchedClassAndPoints.second) {
+      const auto &ClusterId = Clustering_.getClusterIdForPoint(PointId);
+      if (!ClusterId.isValid())
+        continue; // Ignore noise and errors.
+      ClustersForSchedClass.insert(ClusterId.getId());
+    }
+    if (ClustersForSchedClass.size() <= 1)
+      continue; // Nothing weird.
+
+    OS << "\nSched Class ";
+#if !defined(NDEBUG) || defined(LLVM_ENABLE_DUMP)
+    const auto &SchedModel = SubtargetInfo_->getSchedModel();
+    const llvm::MCSchedClassDesc *const SCDesc =
+        SchedModel.getSchedClassDesc(SchedClassAndPoints.first);
+    OS << SCDesc->Name;
+#else
+    OS << SchedClassAndPoints.first;
+#endif
+    OS << " contains instructions with distinct performance "
+          "characteristics, falling into "
+       << ClustersForSchedClass.size() << " clusters:\n";
+    for (const size_t PointId : SchedClassAndPoints.second) {
+      printInstructionRow(/*PrintSchedClass*/ false, PointId, OS);
+    }
+  }
+  return llvm::Error::success();
+}
+
 } // namespace exegesis