Analysis.cpp 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605
  1. //===-- Analysis.cpp --------------------------------------------*- C++ -*-===//
  2. //
  3. // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
  4. // See https://llvm.org/LICENSE.txt for license information.
  5. // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
  6. //
  7. //===----------------------------------------------------------------------===//
  8. #include "Analysis.h"
  9. #include "BenchmarkResult.h"
  10. #include "llvm/ADT/STLExtras.h"
  11. #include "llvm/MC/MCAsmInfo.h"
  12. #include "llvm/MC/MCTargetOptions.h"
  13. #include "llvm/Support/FormatVariadic.h"
  14. #include <limits>
  15. #include <unordered_set>
  16. #include <vector>
  17. namespace llvm {
  18. namespace exegesis {
  19. static const char kCsvSep = ',';
  20. namespace {
  21. enum EscapeTag { kEscapeCsv, kEscapeHtml, kEscapeHtmlString };
  22. template <EscapeTag Tag> void writeEscaped(raw_ostream &OS, const StringRef S);
  23. template <> void writeEscaped<kEscapeCsv>(raw_ostream &OS, const StringRef S) {
  24. if (!llvm::is_contained(S, kCsvSep)) {
  25. OS << S;
  26. } else {
  27. // Needs escaping.
  28. OS << '"';
  29. for (const char C : S) {
  30. if (C == '"')
  31. OS << "\"\"";
  32. else
  33. OS << C;
  34. }
  35. OS << '"';
  36. }
  37. }
  38. template <> void writeEscaped<kEscapeHtml>(raw_ostream &OS, const StringRef S) {
  39. for (const char C : S) {
  40. if (C == '<')
  41. OS << "&lt;";
  42. else if (C == '>')
  43. OS << "&gt;";
  44. else if (C == '&')
  45. OS << "&amp;";
  46. else
  47. OS << C;
  48. }
  49. }
  50. template <>
  51. void writeEscaped<kEscapeHtmlString>(raw_ostream &OS, const StringRef S) {
  52. for (const char C : S) {
  53. if (C == '"')
  54. OS << "\\\"";
  55. else
  56. OS << C;
  57. }
  58. }
  59. } // namespace
  60. template <EscapeTag Tag>
  61. static void
  62. writeClusterId(raw_ostream &OS,
  63. const InstructionBenchmarkClustering::ClusterId &CID) {
  64. if (CID.isNoise())
  65. writeEscaped<Tag>(OS, "[noise]");
  66. else if (CID.isError())
  67. writeEscaped<Tag>(OS, "[error]");
  68. else
  69. OS << CID.getId();
  70. }
  71. template <EscapeTag Tag>
  72. static void writeMeasurementValue(raw_ostream &OS, const double Value) {
  73. // Given Value, if we wanted to serialize it to a string,
  74. // how many base-10 digits will we need to store, max?
  75. static constexpr auto MaxDigitCount =
  76. std::numeric_limits<decltype(Value)>::max_digits10;
  77. // Also, we will need a decimal separator.
  78. static constexpr auto DecimalSeparatorLen = 1; // '.' e.g.
  79. // So how long of a string will the serialization produce, max?
  80. static constexpr auto SerializationLen = MaxDigitCount + DecimalSeparatorLen;
  81. // WARNING: when changing the format, also adjust the small-size estimate ^.
  82. static constexpr StringLiteral SimpleFloatFormat = StringLiteral("{0:F}");
  83. writeEscaped<Tag>(
  84. OS, formatv(SimpleFloatFormat.data(), Value).sstr<SerializationLen>());
  85. }
  86. template <typename EscapeTag, EscapeTag Tag>
  87. void Analysis::writeSnippet(raw_ostream &OS, ArrayRef<uint8_t> Bytes,
  88. const char *Separator) const {
  89. SmallVector<std::string, 3> Lines;
  90. // Parse the asm snippet and print it.
  91. while (!Bytes.empty()) {
  92. MCInst MI;
  93. uint64_t MISize = 0;
  94. if (!Disasm_->getInstruction(MI, MISize, Bytes, 0, nulls())) {
  95. writeEscaped<Tag>(OS, join(Lines, Separator));
  96. writeEscaped<Tag>(OS, Separator);
  97. writeEscaped<Tag>(OS, "[error decoding asm snippet]");
  98. return;
  99. }
  100. SmallString<128> InstPrinterStr; // FIXME: magic number.
  101. raw_svector_ostream OSS(InstPrinterStr);
  102. InstPrinter_->printInst(&MI, 0, "", *SubtargetInfo_, OSS);
  103. Bytes = Bytes.drop_front(MISize);
  104. Lines.emplace_back(StringRef(InstPrinterStr).trim());
  105. }
  106. writeEscaped<Tag>(OS, join(Lines, Separator));
  107. }
  108. // Prints a row representing an instruction, along with scheduling info and
  109. // point coordinates (measurements).
  110. void Analysis::printInstructionRowCsv(const size_t PointId,
  111. raw_ostream &OS) const {
  112. const InstructionBenchmark &Point = Clustering_.getPoints()[PointId];
  113. writeClusterId<kEscapeCsv>(OS, Clustering_.getClusterIdForPoint(PointId));
  114. OS << kCsvSep;
  115. writeSnippet<EscapeTag, kEscapeCsv>(OS, Point.AssembledSnippet, "; ");
  116. OS << kCsvSep;
  117. writeEscaped<kEscapeCsv>(OS, Point.Key.Config);
  118. OS << kCsvSep;
  119. assert(!Point.Key.Instructions.empty());
  120. const MCInst &MCI = Point.keyInstruction();
  121. unsigned SchedClassId;
  122. std::tie(SchedClassId, std::ignore) = ResolvedSchedClass::resolveSchedClassId(
  123. *SubtargetInfo_, *InstrInfo_, MCI);
  124. #if !defined(NDEBUG) || defined(LLVM_ENABLE_DUMP)
  125. const MCSchedClassDesc *const SCDesc =
  126. SubtargetInfo_->getSchedModel().getSchedClassDesc(SchedClassId);
  127. writeEscaped<kEscapeCsv>(OS, SCDesc->Name);
  128. #else
  129. OS << SchedClassId;
  130. #endif
  131. for (const auto &Measurement : Point.Measurements) {
  132. OS << kCsvSep;
  133. writeMeasurementValue<kEscapeCsv>(OS, Measurement.PerInstructionValue);
  134. }
  135. OS << "\n";
  136. }
  137. Analysis::Analysis(const Target &Target, std::unique_ptr<MCInstrInfo> InstrInfo,
  138. const InstructionBenchmarkClustering &Clustering,
  139. double AnalysisInconsistencyEpsilon,
  140. bool AnalysisDisplayUnstableOpcodes,
  141. const std::string &ForceCpuName)
  142. : Clustering_(Clustering), InstrInfo_(std::move(InstrInfo)),
  143. AnalysisInconsistencyEpsilonSquared_(AnalysisInconsistencyEpsilon *
  144. AnalysisInconsistencyEpsilon),
  145. AnalysisDisplayUnstableOpcodes_(AnalysisDisplayUnstableOpcodes) {
  146. if (Clustering.getPoints().empty())
  147. return;
  148. const InstructionBenchmark &FirstPoint = Clustering.getPoints().front();
  149. const std::string CpuName =
  150. ForceCpuName.empty() ? FirstPoint.CpuName : ForceCpuName;
  151. RegInfo_.reset(Target.createMCRegInfo(FirstPoint.LLVMTriple));
  152. MCTargetOptions MCOptions;
  153. AsmInfo_.reset(
  154. Target.createMCAsmInfo(*RegInfo_, FirstPoint.LLVMTriple, MCOptions));
  155. SubtargetInfo_.reset(
  156. Target.createMCSubtargetInfo(FirstPoint.LLVMTriple, CpuName, ""));
  157. InstPrinter_.reset(Target.createMCInstPrinter(
  158. Triple(FirstPoint.LLVMTriple), 0 /*default variant*/, *AsmInfo_,
  159. *InstrInfo_, *RegInfo_));
  160. Context_ = std::make_unique<MCContext>(AsmInfo_.get(), RegInfo_.get(),
  161. &ObjectFileInfo_);
  162. Disasm_.reset(Target.createMCDisassembler(*SubtargetInfo_, *Context_));
  163. assert(Disasm_ && "cannot create MCDisassembler. missing call to "
  164. "InitializeXXXTargetDisassembler ?");
  165. }
  166. template <>
  167. Error Analysis::run<Analysis::PrintClusters>(raw_ostream &OS) const {
  168. if (Clustering_.getPoints().empty())
  169. return Error::success();
  170. // Write the header.
  171. OS << "cluster_id" << kCsvSep << "opcode_name" << kCsvSep << "config"
  172. << kCsvSep << "sched_class";
  173. for (const auto &Measurement : Clustering_.getPoints().front().Measurements) {
  174. OS << kCsvSep;
  175. writeEscaped<kEscapeCsv>(OS, Measurement.Key);
  176. }
  177. OS << "\n";
  178. // Write the points.
  179. const auto &Clusters = Clustering_.getValidClusters();
  180. for (size_t I = 0, E = Clusters.size(); I < E; ++I) {
  181. for (const size_t PointId : Clusters[I].PointIndices) {
  182. printInstructionRowCsv(PointId, OS);
  183. }
  184. OS << "\n\n";
  185. }
  186. return Error::success();
  187. }
  188. Analysis::ResolvedSchedClassAndPoints::ResolvedSchedClassAndPoints(
  189. ResolvedSchedClass &&RSC)
  190. : RSC(std::move(RSC)) {}
  191. std::vector<Analysis::ResolvedSchedClassAndPoints>
  192. Analysis::makePointsPerSchedClass() const {
  193. std::vector<ResolvedSchedClassAndPoints> Entries;
  194. // Maps SchedClassIds to index in result.
  195. std::unordered_map<unsigned, size_t> SchedClassIdToIndex;
  196. const auto &Points = Clustering_.getPoints();
  197. for (size_t PointId = 0, E = Points.size(); PointId < E; ++PointId) {
  198. const InstructionBenchmark &Point = Points[PointId];
  199. if (!Point.Error.empty())
  200. continue;
  201. assert(!Point.Key.Instructions.empty());
  202. // FIXME: we should be using the tuple of classes for instructions in the
  203. // snippet as key.
  204. const MCInst &MCI = Point.keyInstruction();
  205. unsigned SchedClassId;
  206. bool WasVariant;
  207. std::tie(SchedClassId, WasVariant) =
  208. ResolvedSchedClass::resolveSchedClassId(*SubtargetInfo_, *InstrInfo_,
  209. MCI);
  210. const auto IndexIt = SchedClassIdToIndex.find(SchedClassId);
  211. if (IndexIt == SchedClassIdToIndex.end()) {
  212. // Create a new entry.
  213. SchedClassIdToIndex.emplace(SchedClassId, Entries.size());
  214. ResolvedSchedClassAndPoints Entry(
  215. ResolvedSchedClass(*SubtargetInfo_, SchedClassId, WasVariant));
  216. Entry.PointIds.push_back(PointId);
  217. Entries.push_back(std::move(Entry));
  218. } else {
  219. // Append to the existing entry.
  220. Entries[IndexIt->second].PointIds.push_back(PointId);
  221. }
  222. }
  223. return Entries;
  224. }
  225. // Parallel benchmarks repeat the same opcode multiple times. Just show this
  226. // opcode and show the whole snippet only on hover.
  227. static void writeParallelSnippetHtml(raw_ostream &OS,
  228. const std::vector<MCInst> &Instructions,
  229. const MCInstrInfo &InstrInfo) {
  230. if (Instructions.empty())
  231. return;
  232. writeEscaped<kEscapeHtml>(OS, InstrInfo.getName(Instructions[0].getOpcode()));
  233. if (Instructions.size() > 1)
  234. OS << " (x" << Instructions.size() << ")";
  235. }
  236. // Latency tries to find a serial path. Just show the opcode path and show the
  237. // whole snippet only on hover.
  238. static void writeLatencySnippetHtml(raw_ostream &OS,
  239. const std::vector<MCInst> &Instructions,
  240. const MCInstrInfo &InstrInfo) {
  241. bool First = true;
  242. for (const MCInst &Instr : Instructions) {
  243. if (First)
  244. First = false;
  245. else
  246. OS << " &rarr; ";
  247. writeEscaped<kEscapeHtml>(OS, InstrInfo.getName(Instr.getOpcode()));
  248. }
  249. }
  250. void Analysis::printPointHtml(const InstructionBenchmark &Point,
  251. llvm::raw_ostream &OS) const {
  252. OS << "<li><span class=\"mono\" title=\"";
  253. writeSnippet<EscapeTag, kEscapeHtmlString>(OS, Point.AssembledSnippet, "\n");
  254. OS << "\">";
  255. switch (Point.Mode) {
  256. case InstructionBenchmark::Latency:
  257. writeLatencySnippetHtml(OS, Point.Key.Instructions, *InstrInfo_);
  258. break;
  259. case InstructionBenchmark::Uops:
  260. case InstructionBenchmark::InverseThroughput:
  261. writeParallelSnippetHtml(OS, Point.Key.Instructions, *InstrInfo_);
  262. break;
  263. default:
  264. llvm_unreachable("invalid mode");
  265. }
  266. OS << "</span> <span class=\"mono\">";
  267. writeEscaped<kEscapeHtml>(OS, Point.Key.Config);
  268. OS << "</span></li>";
  269. }
  270. void Analysis::printSchedClassClustersHtml(
  271. const std::vector<SchedClassCluster> &Clusters,
  272. const ResolvedSchedClass &RSC, raw_ostream &OS) const {
  273. const auto &Points = Clustering_.getPoints();
  274. OS << "<table class=\"sched-class-clusters\">";
  275. OS << "<tr><th>ClusterId</th><th>Opcode/Config</th>";
  276. assert(!Clusters.empty());
  277. for (const auto &Measurement :
  278. Points[Clusters[0].getPointIds()[0]].Measurements) {
  279. OS << "<th>";
  280. writeEscaped<kEscapeHtml>(OS, Measurement.Key);
  281. OS << "</th>";
  282. }
  283. OS << "</tr>";
  284. for (const SchedClassCluster &Cluster : Clusters) {
  285. OS << "<tr class=\""
  286. << (Cluster.measurementsMatch(*SubtargetInfo_, RSC, Clustering_,
  287. AnalysisInconsistencyEpsilonSquared_)
  288. ? "good-cluster"
  289. : "bad-cluster")
  290. << "\"><td>";
  291. writeClusterId<kEscapeHtml>(OS, Cluster.id());
  292. OS << "</td><td><ul>";
  293. for (const size_t PointId : Cluster.getPointIds()) {
  294. printPointHtml(Points[PointId], OS);
  295. }
  296. OS << "</ul></td>";
  297. for (const auto &Stats : Cluster.getCentroid().getStats()) {
  298. OS << "<td class=\"measurement\">";
  299. writeMeasurementValue<kEscapeHtml>(OS, Stats.avg());
  300. OS << "<br><span class=\"minmax\">[";
  301. writeMeasurementValue<kEscapeHtml>(OS, Stats.min());
  302. OS << ";";
  303. writeMeasurementValue<kEscapeHtml>(OS, Stats.max());
  304. OS << "]</span></td>";
  305. }
  306. OS << "</tr>";
  307. }
  308. OS << "</table>";
  309. }
  310. void Analysis::SchedClassCluster::addPoint(
  311. size_t PointId, const InstructionBenchmarkClustering &Clustering) {
  312. PointIds.push_back(PointId);
  313. const auto &Point = Clustering.getPoints()[PointId];
  314. if (ClusterId.isUndef())
  315. ClusterId = Clustering.getClusterIdForPoint(PointId);
  316. assert(ClusterId == Clustering.getClusterIdForPoint(PointId));
  317. Centroid.addPoint(Point.Measurements);
  318. }
  319. bool Analysis::SchedClassCluster::measurementsMatch(
  320. const MCSubtargetInfo &STI, const ResolvedSchedClass &RSC,
  321. const InstructionBenchmarkClustering &Clustering,
  322. const double AnalysisInconsistencyEpsilonSquared_) const {
  323. assert(!Clustering.getPoints().empty());
  324. const InstructionBenchmark::ModeE Mode = Clustering.getPoints()[0].Mode;
  325. if (!Centroid.validate(Mode))
  326. return false;
  327. const std::vector<BenchmarkMeasure> ClusterCenterPoint =
  328. Centroid.getAsPoint();
  329. const std::vector<BenchmarkMeasure> SchedClassPoint =
  330. RSC.getAsPoint(Mode, STI, Centroid.getStats());
  331. if (SchedClassPoint.empty())
  332. return false; // In Uops mode validate() may not be enough.
  333. assert(ClusterCenterPoint.size() == SchedClassPoint.size() &&
  334. "Expected measured/sched data dimensions to match.");
  335. return Clustering.isNeighbour(ClusterCenterPoint, SchedClassPoint,
  336. AnalysisInconsistencyEpsilonSquared_);
  337. }
  338. void Analysis::printSchedClassDescHtml(const ResolvedSchedClass &RSC,
  339. raw_ostream &OS) const {
  340. OS << "<table class=\"sched-class-desc\">";
  341. OS << "<tr><th>Valid</th><th>Variant</th><th>NumMicroOps</th><th>Latency</"
  342. "th><th>RThroughput</th><th>WriteProcRes</th><th title=\"This is the "
  343. "idealized unit resource (port) pressure assuming ideal "
  344. "distribution\">Idealized Resource Pressure</th></tr>";
  345. if (RSC.SCDesc->isValid()) {
  346. const auto &SM = SubtargetInfo_->getSchedModel();
  347. OS << "<tr><td>&#10004;</td>";
  348. OS << "<td>" << (RSC.WasVariant ? "&#10004;" : "&#10005;") << "</td>";
  349. OS << "<td>" << RSC.SCDesc->NumMicroOps << "</td>";
  350. // Latencies.
  351. OS << "<td><ul>";
  352. for (int I = 0, E = RSC.SCDesc->NumWriteLatencyEntries; I < E; ++I) {
  353. const auto *const Entry =
  354. SubtargetInfo_->getWriteLatencyEntry(RSC.SCDesc, I);
  355. OS << "<li>" << Entry->Cycles;
  356. if (RSC.SCDesc->NumWriteLatencyEntries > 1) {
  357. // Dismabiguate if more than 1 latency.
  358. OS << " (WriteResourceID " << Entry->WriteResourceID << ")";
  359. }
  360. OS << "</li>";
  361. }
  362. OS << "</ul></td>";
  363. // inverse throughput.
  364. OS << "<td>";
  365. writeMeasurementValue<kEscapeHtml>(
  366. OS,
  367. MCSchedModel::getReciprocalThroughput(*SubtargetInfo_, *RSC.SCDesc));
  368. OS << "</td>";
  369. // WriteProcRes.
  370. OS << "<td><ul>";
  371. for (const auto &WPR : RSC.NonRedundantWriteProcRes) {
  372. OS << "<li><span class=\"mono\">";
  373. writeEscaped<kEscapeHtml>(OS,
  374. SM.getProcResource(WPR.ProcResourceIdx)->Name);
  375. OS << "</span>: " << WPR.Cycles << "</li>";
  376. }
  377. OS << "</ul></td>";
  378. // Idealized port pressure.
  379. OS << "<td><ul>";
  380. for (const auto &Pressure : RSC.IdealizedProcResPressure) {
  381. OS << "<li><span class=\"mono\">";
  382. writeEscaped<kEscapeHtml>(OS, SubtargetInfo_->getSchedModel()
  383. .getProcResource(Pressure.first)
  384. ->Name);
  385. OS << "</span>: ";
  386. writeMeasurementValue<kEscapeHtml>(OS, Pressure.second);
  387. OS << "</li>";
  388. }
  389. OS << "</ul></td>";
  390. OS << "</tr>";
  391. } else {
  392. OS << "<tr><td>&#10005;</td><td></td><td></td></tr>";
  393. }
  394. OS << "</table>";
  395. }
  396. void Analysis::printClusterRawHtml(
  397. const InstructionBenchmarkClustering::ClusterId &Id, StringRef display_name,
  398. llvm::raw_ostream &OS) const {
  399. const auto &Points = Clustering_.getPoints();
  400. const auto &Cluster = Clustering_.getCluster(Id);
  401. if (Cluster.PointIndices.empty())
  402. return;
  403. OS << "<div class=\"inconsistency\"><p>" << display_name << " Cluster ("
  404. << Cluster.PointIndices.size() << " points)</p>";
  405. OS << "<table class=\"sched-class-clusters\">";
  406. // Table Header.
  407. OS << "<tr><th>ClusterId</th><th>Opcode/Config</th>";
  408. for (const auto &Measurement : Points[Cluster.PointIndices[0]].Measurements) {
  409. OS << "<th>";
  410. writeEscaped<kEscapeHtml>(OS, Measurement.Key);
  411. OS << "</th>";
  412. }
  413. OS << "</tr>";
  414. // Point data.
  415. for (const auto &PointId : Cluster.PointIndices) {
  416. OS << "<tr class=\"bad-cluster\"><td>" << display_name << "</td><td><ul>";
  417. printPointHtml(Points[PointId], OS);
  418. OS << "</ul></td>";
  419. for (const auto &Measurement : Points[PointId].Measurements) {
  420. OS << "<td class=\"measurement\">";
  421. writeMeasurementValue<kEscapeHtml>(OS, Measurement.PerInstructionValue);
  422. }
  423. OS << "</tr>";
  424. }
  425. OS << "</table>";
  426. OS << "</div>";
  427. } // namespace exegesis
  428. static constexpr const char kHtmlHead[] = R"(
  429. <head>
  430. <title>llvm-exegesis Analysis Results</title>
  431. <style>
  432. body {
  433. font-family: sans-serif
  434. }
  435. span.sched-class-name {
  436. font-weight: bold;
  437. font-family: monospace;
  438. }
  439. span.opcode {
  440. font-family: monospace;
  441. }
  442. span.config {
  443. font-family: monospace;
  444. }
  445. div.inconsistency {
  446. margin-top: 50px;
  447. }
  448. table {
  449. margin-left: 50px;
  450. border-collapse: collapse;
  451. }
  452. table, table tr,td,th {
  453. border: 1px solid #444;
  454. }
  455. table ul {
  456. padding-left: 0px;
  457. margin: 0px;
  458. list-style-type: none;
  459. }
  460. table.sched-class-clusters td {
  461. padding-left: 10px;
  462. padding-right: 10px;
  463. padding-top: 10px;
  464. padding-bottom: 10px;
  465. }
  466. table.sched-class-desc td {
  467. padding-left: 10px;
  468. padding-right: 10px;
  469. padding-top: 2px;
  470. padding-bottom: 2px;
  471. }
  472. span.mono {
  473. font-family: monospace;
  474. }
  475. td.measurement {
  476. text-align: center;
  477. }
  478. tr.good-cluster td.measurement {
  479. color: #292
  480. }
  481. tr.bad-cluster td.measurement {
  482. color: #922
  483. }
  484. tr.good-cluster td.measurement span.minmax {
  485. color: #888;
  486. }
  487. tr.bad-cluster td.measurement span.minmax {
  488. color: #888;
  489. }
  490. </style>
  491. </head>
  492. )";
  493. template <>
  494. Error Analysis::run<Analysis::PrintSchedClassInconsistencies>(
  495. raw_ostream &OS) const {
  496. const auto &FirstPoint = Clustering_.getPoints()[0];
  497. // Print the header.
  498. OS << "<!DOCTYPE html><html>" << kHtmlHead << "<body>";
  499. OS << "<h1><span class=\"mono\">llvm-exegesis</span> Analysis Results</h1>";
  500. OS << "<h3>Triple: <span class=\"mono\">";
  501. writeEscaped<kEscapeHtml>(OS, FirstPoint.LLVMTriple);
  502. OS << "</span></h3><h3>Cpu: <span class=\"mono\">";
  503. writeEscaped<kEscapeHtml>(OS, FirstPoint.CpuName);
  504. OS << "</span></h3>";
  505. for (const auto &RSCAndPoints : makePointsPerSchedClass()) {
  506. if (!RSCAndPoints.RSC.SCDesc)
  507. continue;
  508. // Bucket sched class points into sched class clusters.
  509. std::vector<SchedClassCluster> SchedClassClusters;
  510. for (const size_t PointId : RSCAndPoints.PointIds) {
  511. const auto &ClusterId = Clustering_.getClusterIdForPoint(PointId);
  512. if (!ClusterId.isValid())
  513. continue; // Ignore noise and errors. FIXME: take noise into account ?
  514. if (ClusterId.isUnstable() ^ AnalysisDisplayUnstableOpcodes_)
  515. continue; // Either display stable or unstable clusters only.
  516. auto SchedClassClusterIt = llvm::find_if(
  517. SchedClassClusters, [ClusterId](const SchedClassCluster &C) {
  518. return C.id() == ClusterId;
  519. });
  520. if (SchedClassClusterIt == SchedClassClusters.end()) {
  521. SchedClassClusters.emplace_back();
  522. SchedClassClusterIt = std::prev(SchedClassClusters.end());
  523. }
  524. SchedClassClusterIt->addPoint(PointId, Clustering_);
  525. }
  526. // Print any scheduling class that has at least one cluster that does not
  527. // match the checked-in data.
  528. if (all_of(SchedClassClusters, [this,
  529. &RSCAndPoints](const SchedClassCluster &C) {
  530. return C.measurementsMatch(*SubtargetInfo_, RSCAndPoints.RSC,
  531. Clustering_,
  532. AnalysisInconsistencyEpsilonSquared_);
  533. }))
  534. continue; // Nothing weird.
  535. OS << "<div class=\"inconsistency\"><p>Sched Class <span "
  536. "class=\"sched-class-name\">";
  537. #if !defined(NDEBUG) || defined(LLVM_ENABLE_DUMP)
  538. writeEscaped<kEscapeHtml>(OS, RSCAndPoints.RSC.SCDesc->Name);
  539. #else
  540. OS << RSCAndPoints.RSC.SchedClassId;
  541. #endif
  542. OS << "</span> contains instructions whose performance characteristics do"
  543. " not match that of LLVM:</p>";
  544. printSchedClassClustersHtml(SchedClassClusters, RSCAndPoints.RSC, OS);
  545. OS << "<p>llvm SchedModel data:</p>";
  546. printSchedClassDescHtml(RSCAndPoints.RSC, OS);
  547. OS << "</div>";
  548. }
  549. printClusterRawHtml(InstructionBenchmarkClustering::ClusterId::noise(),
  550. "[noise]", OS);
  551. OS << "</body></html>";
  552. return Error::success();
  553. }
  554. } // namespace exegesis
  555. } // namespace llvm