Index: tools/llvm-exegesis/lib/Analysis.h =================================================================== --- tools/llvm-exegesis/lib/Analysis.h +++ tools/llvm-exegesis/lib/Analysis.h @@ -40,7 +40,9 @@ template llvm::Error run(llvm::raw_ostream &OS) const; private: - void printInstructionRow(bool PrintSchedClass, size_t PointId, + void printInstructionRowCsv(size_t PointId, llvm::raw_ostream &OS) const; + + void printSchedClassHtml(std::vector PointIds, llvm::raw_ostream &OS) const; // Builds a map of Sched Class -> indices of points that belong to the sched Index: tools/llvm-exegesis/lib/Analysis.cpp =================================================================== --- tools/llvm-exegesis/lib/Analysis.cpp +++ tools/llvm-exegesis/lib/Analysis.cpp @@ -17,7 +17,15 @@ static const char kCsvSep = ','; -static void writeCsvEscaped(llvm::raw_ostream &OS, const std::string &S) { +namespace { + +enum EscapeTag { kEscapeCsv, kEscapeHtml }; + +template +void writeEscaped(llvm::raw_ostream &OS, const llvm::StringRef S); + +template <> +void writeEscaped(llvm::raw_ostream &OS, const llvm::StringRef S) { if (std::find(S.begin(), S.end(), kCsvSep) == S.end()) { OS << S; } else { @@ -33,44 +41,68 @@ } } +template <> +void writeEscaped(llvm::raw_ostream &OS, const llvm::StringRef S) { + for (const char C : S) { + if (C == '<') + OS << "<"; + else if (C == '>') + OS << ">"; + else if (C == '&') + OS << "&"; + else + OS << C; + } +} + +} // namespace + +template +static void +writeClusterId(llvm::raw_ostream &OS, + const InstructionBenchmarkClustering::ClusterId &CID) { + if (CID.isNoise()) + writeEscaped(OS, "[noise]"); + else if (CID.isError()) + writeEscaped(OS, "[error]"); + else + OS << CID.getId(); +} + +template +static void writeMeasurementValue(llvm::raw_ostream &OS, const double Value) { + writeEscaped(OS, llvm::formatv("{0:F}", Value).str()); +} + // Prints a row representing an instruction, along with scheduling info and // point coordinates (measurements). -void Analysis::printInstructionRow(const bool PrintSchedClass, - const size_t PointId, - llvm::raw_ostream &OS) const { +void Analysis::printInstructionRowCsv(const size_t PointId, + llvm::raw_ostream &OS) const { const InstructionBenchmark &Point = Clustering_.getPoints()[PointId]; - const auto &ClusterId = Clustering_.getClusterIdForPoint(PointId); - if (ClusterId.isNoise()) - OS << "[noise]"; - else if (ClusterId.isError()) - OS << "[error]"; - else - OS << ClusterId.getId(); + writeClusterId(OS, Clustering_.getClusterIdForPoint(PointId)); OS << kCsvSep; - writeCsvEscaped(OS, Point.Key.OpcodeName); + writeEscaped(OS, Point.Key.OpcodeName); OS << kCsvSep; - writeCsvEscaped(OS, Point.Key.Config); - if (PrintSchedClass) { - OS << kCsvSep; - const auto OpcodeIt = MnemonicToOpcode_.find(Point.Key.OpcodeName); - if (OpcodeIt != MnemonicToOpcode_.end()) { - const unsigned SchedClassId = - InstrInfo_->get(OpcodeIt->second).getSchedClass(); + writeEscaped(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 !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); + writeEscaped(OS, SCDesc->Name); #else - OS << SchedClassId; + OS << SchedClassId; #endif - } } // FIXME: Print the sched class once InstructionBenchmark separates key into // (mnemonic, mode, opaque). for (const auto &Measurement : Point.Measurements) { OS << kCsvSep; - writeCsvEscaped(OS, llvm::formatv("{0:F}", Measurement.Value)); + writeMeasurementValue(OS, Measurement.Value); } OS << "\n"; } @@ -102,7 +134,7 @@ << kCsvSep << "sched_class"; for (const auto &Measurement : Clustering_.getPoints().front().Measurements) { OS << kCsvSep; - writeCsvEscaped(OS, Measurement.Key); + writeEscaped(OS, Measurement.Key); } OS << "\n"; @@ -110,7 +142,7 @@ 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(/*PrintSchedClass*/ true, PointId, OS); + printInstructionRowCsv(PointId, OS); } OS << "\n\n"; } @@ -135,9 +167,110 @@ return PointsPerSchedClass; } +void Analysis::printSchedClassHtml(std::vector PointIds, + llvm::raw_ostream &OS) const { + assert(!PointIds.empty()); + // Sort the points by cluster id so that we can display them grouped by + // cluster. + std::sort(PointIds.begin(), PointIds.end(), + [this](const size_t A, const size_t B) { + return Clustering_.getClusterIdForPoint(A) < + Clustering_.getClusterIdForPoint(B); + }); + const auto &Points = Clustering_.getPoints(); + OS << ""; + OS << ""; + for (const auto &Measurement : Points[PointIds[0]].Measurements) { + OS << ""; + } + OS << ""; + for (size_t I = 0, E = PointIds.size(); I < E;) { + const auto &CurrentClusterId = + Clustering_.getClusterIdForPoint(PointIds[I]); + OS << ""; + for (const auto &Measurement : ClusterRepresentative.Measurements) { + OS << ""; + } + OS << ""; + } + OS << "
ClusterIdOpcode/Config"; + writeEscaped(OS, Measurement.Key); + OS << "
"; + writeClusterId(OS, CurrentClusterId); + OS << "
    "; + const auto &ClusterRepresentative = + Points[PointIds[I]]; // FIXME: average measurements. + for (; I < E && + Clustering_.getClusterIdForPoint(PointIds[I]) == CurrentClusterId; + ++I) { + OS << "
  • "; + writeEscaped(OS, Points[PointIds[I]].Key.OpcodeName); + OS << " "; + writeEscaped(OS, Points[PointIds[I]].Key.Config); + OS << "
  • "; + } + OS << "
"; + writeMeasurementValue(OS, Measurement.Value); + OS << "
"; +} + +static constexpr const char kHtmlHead[] = R"( + +llvm-exegesis Analysis Results + + +)"; + template <> llvm::Error Analysis::run( llvm::raw_ostream &OS) const { + // Print the header. + OS << "" << kHtmlHead << ""; + OS << "

llvm-exegesis Analysis Results

"; + OS << "

Triple: "; + writeEscaped(OS, Clustering_.getPoints()[0].LLVMTriple); + OS << "

Cpu: "; + writeEscaped(OS, Clustering_.getPoints()[0].CpuName); + OS << "

"; + // 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()) { @@ -151,22 +284,24 @@ if (ClustersForSchedClass.size() <= 1) continue; // Nothing weird. - OS << "\nSched Class "; + OS << "

Sched 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; + writeEscaped(OS, SCDesc->Name); #else OS << SchedClassAndPoints.first; #endif - OS << " contains instructions with distinct performance " + 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); - } + << ClustersForSchedClass.size() << " clusters:

"; + printSchedClassHtml(SchedClassAndPoints.second, OS); + OS << "
"; } + + OS << ""; return llvm::Error::success(); } Index: tools/llvm-exegesis/lib/Clustering.h =================================================================== --- tools/llvm-exegesis/lib/Clustering.h +++ tools/llvm-exegesis/lib/Clustering.h @@ -33,14 +33,14 @@ public: static ClusterId noise() { return ClusterId(kNoise); } static ClusterId error() { return ClusterId(kError); } - static ClusterId makeValid(int Id) { - assert(Id >= 0); + static ClusterId makeValid(size_t Id) { return ClusterId(Id); } ClusterId() : Id_(kUndef) {} bool operator==(const ClusterId &O) const { return Id_ == O.Id_; } + bool operator<(const ClusterId &O) const {return Id_ < O.Id_; } - bool isValid() const { return Id_ >= 0; } + bool isValid() const { return Id_ <= kMaxValid; } bool isUndef() const { return Id_ == kUndef; } bool isNoise() const { return Id_ == kNoise; } bool isError() const { return Id_ == kError; } @@ -48,15 +48,16 @@ // Precondition: isValid(). size_t getId() const { assert(isValid()); - return static_cast(Id_); + return Id_; } private: - explicit ClusterId(int Id) : Id_(Id) {} - static constexpr const int kUndef = -1; - static constexpr const int kNoise = -2; - static constexpr const int kError = -3; - int Id_; + explicit ClusterId(size_t Id) : Id_(Id) {} + static constexpr const size_t kMaxValid = std::numeric_limits::max() - 4; + static constexpr const size_t kNoise = kMaxValid + 1; + static constexpr const size_t kError = kMaxValid + 2; + static constexpr const size_t kUndef = kMaxValid + 3; + size_t Id_; }; struct Cluster { Index: unittests/tools/llvm-exegesis/ClusteringTest.cpp =================================================================== --- unittests/tools/llvm-exegesis/ClusteringTest.cpp +++ unittests/tools/llvm-exegesis/ClusteringTest.cpp @@ -82,5 +82,19 @@ consumeError(std::move(Error)); } +TEST(ClusteringTest, Ordering) { + ASSERT_LT(InstructionBenchmarkClustering::ClusterId::makeValid(1), + InstructionBenchmarkClustering::ClusterId::makeValid(2)); + + ASSERT_LT(InstructionBenchmarkClustering::ClusterId::makeValid(2), + InstructionBenchmarkClustering::ClusterId::noise()); + + ASSERT_LT(InstructionBenchmarkClustering::ClusterId::makeValid(2), + InstructionBenchmarkClustering::ClusterId::error()); + + ASSERT_LT(InstructionBenchmarkClustering::ClusterId::noise(), + InstructionBenchmarkClustering::ClusterId::error()); +} + } // namespace } // namespace exegesis