Analysis.cpp 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608
  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(InstPrinterStr.str().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,
  138. std::unique_ptr<MCSubtargetInfo> SubtargetInfo,
  139. std::unique_ptr<MCInstrInfo> InstrInfo,
  140. const InstructionBenchmarkClustering &Clustering,
  141. double AnalysisInconsistencyEpsilon,
  142. bool AnalysisDisplayUnstableOpcodes,
  143. const std::string &ForceCpuName)
  144. : Clustering_(Clustering), SubtargetInfo_(std::move(SubtargetInfo)),
  145. InstrInfo_(std::move(InstrInfo)),
  146. AnalysisInconsistencyEpsilonSquared_(AnalysisInconsistencyEpsilon *
  147. AnalysisInconsistencyEpsilon),
  148. AnalysisDisplayUnstableOpcodes_(AnalysisDisplayUnstableOpcodes) {
  149. if (Clustering.getPoints().empty())
  150. return;
  151. const InstructionBenchmark &FirstPoint = Clustering.getPoints().front();
  152. const std::string CpuName =
  153. ForceCpuName.empty() ? FirstPoint.CpuName : ForceCpuName;
  154. RegInfo_.reset(Target.createMCRegInfo(FirstPoint.LLVMTriple));
  155. MCTargetOptions MCOptions;
  156. AsmInfo_.reset(
  157. Target.createMCAsmInfo(*RegInfo_, FirstPoint.LLVMTriple, MCOptions));
  158. SubtargetInfo_.reset(
  159. Target.createMCSubtargetInfo(FirstPoint.LLVMTriple, CpuName, ""));
  160. InstPrinter_.reset(Target.createMCInstPrinter(
  161. Triple(FirstPoint.LLVMTriple), 0 /*default variant*/, *AsmInfo_,
  162. *InstrInfo_, *RegInfo_));
  163. Context_ =
  164. std::make_unique<MCContext>(Triple(FirstPoint.LLVMTriple), AsmInfo_.get(),
  165. RegInfo_.get(), SubtargetInfo_.get());
  166. Disasm_.reset(Target.createMCDisassembler(*SubtargetInfo_, *Context_));
  167. assert(Disasm_ && "cannot create MCDisassembler. missing call to "
  168. "InitializeXXXTargetDisassembler ?");
  169. }
  170. template <>
  171. Error Analysis::run<Analysis::PrintClusters>(raw_ostream &OS) const {
  172. if (Clustering_.getPoints().empty())
  173. return Error::success();
  174. // Write the header.
  175. OS << "cluster_id" << kCsvSep << "opcode_name" << kCsvSep << "config"
  176. << kCsvSep << "sched_class";
  177. for (const auto &Measurement : Clustering_.getPoints().front().Measurements) {
  178. OS << kCsvSep;
  179. writeEscaped<kEscapeCsv>(OS, Measurement.Key);
  180. }
  181. OS << "\n";
  182. // Write the points.
  183. for (const auto &ClusterIt : Clustering_.getValidClusters()) {
  184. for (const size_t PointId : ClusterIt.PointIndices) {
  185. printInstructionRowCsv(PointId, OS);
  186. }
  187. OS << "\n\n";
  188. }
  189. return Error::success();
  190. }
  191. Analysis::ResolvedSchedClassAndPoints::ResolvedSchedClassAndPoints(
  192. ResolvedSchedClass &&RSC)
  193. : RSC(std::move(RSC)) {}
  194. std::vector<Analysis::ResolvedSchedClassAndPoints>
  195. Analysis::makePointsPerSchedClass() const {
  196. std::vector<ResolvedSchedClassAndPoints> Entries;
  197. // Maps SchedClassIds to index in result.
  198. std::unordered_map<unsigned, size_t> SchedClassIdToIndex;
  199. const auto &Points = Clustering_.getPoints();
  200. for (size_t PointId = 0, E = Points.size(); PointId < E; ++PointId) {
  201. const InstructionBenchmark &Point = Points[PointId];
  202. if (!Point.Error.empty())
  203. continue;
  204. assert(!Point.Key.Instructions.empty());
  205. // FIXME: we should be using the tuple of classes for instructions in the
  206. // snippet as key.
  207. const MCInst &MCI = Point.keyInstruction();
  208. unsigned SchedClassId;
  209. bool WasVariant;
  210. std::tie(SchedClassId, WasVariant) =
  211. ResolvedSchedClass::resolveSchedClassId(*SubtargetInfo_, *InstrInfo_,
  212. MCI);
  213. const auto IndexIt = SchedClassIdToIndex.find(SchedClassId);
  214. if (IndexIt == SchedClassIdToIndex.end()) {
  215. // Create a new entry.
  216. SchedClassIdToIndex.emplace(SchedClassId, Entries.size());
  217. ResolvedSchedClassAndPoints Entry(
  218. ResolvedSchedClass(*SubtargetInfo_, SchedClassId, WasVariant));
  219. Entry.PointIds.push_back(PointId);
  220. Entries.push_back(std::move(Entry));
  221. } else {
  222. // Append to the existing entry.
  223. Entries[IndexIt->second].PointIds.push_back(PointId);
  224. }
  225. }
  226. return Entries;
  227. }
  228. // Parallel benchmarks repeat the same opcode multiple times. Just show this
  229. // opcode and show the whole snippet only on hover.
  230. static void writeParallelSnippetHtml(raw_ostream &OS,
  231. const std::vector<MCInst> &Instructions,
  232. const MCInstrInfo &InstrInfo) {
  233. if (Instructions.empty())
  234. return;
  235. writeEscaped<kEscapeHtml>(OS, InstrInfo.getName(Instructions[0].getOpcode()));
  236. if (Instructions.size() > 1)
  237. OS << " (x" << Instructions.size() << ")";
  238. }
  239. // Latency tries to find a serial path. Just show the opcode path and show the
  240. // whole snippet only on hover.
  241. static void writeLatencySnippetHtml(raw_ostream &OS,
  242. const std::vector<MCInst> &Instructions,
  243. const MCInstrInfo &InstrInfo) {
  244. bool First = true;
  245. for (const MCInst &Instr : Instructions) {
  246. if (First)
  247. First = false;
  248. else
  249. OS << " &rarr; ";
  250. writeEscaped<kEscapeHtml>(OS, InstrInfo.getName(Instr.getOpcode()));
  251. }
  252. }
  253. void Analysis::printPointHtml(const InstructionBenchmark &Point,
  254. llvm::raw_ostream &OS) const {
  255. OS << "<li><span class=\"mono\" title=\"";
  256. writeSnippet<EscapeTag, kEscapeHtmlString>(OS, Point.AssembledSnippet, "\n");
  257. OS << "\">";
  258. switch (Point.Mode) {
  259. case InstructionBenchmark::Latency:
  260. writeLatencySnippetHtml(OS, Point.Key.Instructions, *InstrInfo_);
  261. break;
  262. case InstructionBenchmark::Uops:
  263. case InstructionBenchmark::InverseThroughput:
  264. writeParallelSnippetHtml(OS, Point.Key.Instructions, *InstrInfo_);
  265. break;
  266. default:
  267. llvm_unreachable("invalid mode");
  268. }
  269. OS << "</span> <span class=\"mono\">";
  270. writeEscaped<kEscapeHtml>(OS, Point.Key.Config);
  271. OS << "</span></li>";
  272. }
  273. void Analysis::printSchedClassClustersHtml(
  274. const std::vector<SchedClassCluster> &Clusters,
  275. const ResolvedSchedClass &RSC, raw_ostream &OS) const {
  276. const auto &Points = Clustering_.getPoints();
  277. OS << "<table class=\"sched-class-clusters\">";
  278. OS << "<tr><th>ClusterId</th><th>Opcode/Config</th>";
  279. assert(!Clusters.empty());
  280. for (const auto &Measurement :
  281. Points[Clusters[0].getPointIds()[0]].Measurements) {
  282. OS << "<th>";
  283. writeEscaped<kEscapeHtml>(OS, Measurement.Key);
  284. OS << "</th>";
  285. }
  286. OS << "</tr>";
  287. for (const SchedClassCluster &Cluster : Clusters) {
  288. OS << "<tr class=\""
  289. << (Cluster.measurementsMatch(*SubtargetInfo_, RSC, Clustering_,
  290. AnalysisInconsistencyEpsilonSquared_)
  291. ? "good-cluster"
  292. : "bad-cluster")
  293. << "\"><td>";
  294. writeClusterId<kEscapeHtml>(OS, Cluster.id());
  295. OS << "</td><td><ul>";
  296. for (const size_t PointId : Cluster.getPointIds()) {
  297. printPointHtml(Points[PointId], OS);
  298. }
  299. OS << "</ul></td>";
  300. for (const auto &Stats : Cluster.getCentroid().getStats()) {
  301. OS << "<td class=\"measurement\">";
  302. writeMeasurementValue<kEscapeHtml>(OS, Stats.avg());
  303. OS << "<br><span class=\"minmax\">[";
  304. writeMeasurementValue<kEscapeHtml>(OS, Stats.min());
  305. OS << ";";
  306. writeMeasurementValue<kEscapeHtml>(OS, Stats.max());
  307. OS << "]</span></td>";
  308. }
  309. OS << "</tr>";
  310. }
  311. OS << "</table>";
  312. }
  313. void Analysis::SchedClassCluster::addPoint(
  314. size_t PointId, const InstructionBenchmarkClustering &Clustering) {
  315. PointIds.push_back(PointId);
  316. const auto &Point = Clustering.getPoints()[PointId];
  317. if (ClusterId.isUndef())
  318. ClusterId = Clustering.getClusterIdForPoint(PointId);
  319. assert(ClusterId == Clustering.getClusterIdForPoint(PointId));
  320. Centroid.addPoint(Point.Measurements);
  321. }
  322. bool Analysis::SchedClassCluster::measurementsMatch(
  323. const MCSubtargetInfo &STI, const ResolvedSchedClass &RSC,
  324. const InstructionBenchmarkClustering &Clustering,
  325. const double AnalysisInconsistencyEpsilonSquared_) const {
  326. assert(!Clustering.getPoints().empty());
  327. const InstructionBenchmark::ModeE Mode = Clustering.getPoints()[0].Mode;
  328. if (!Centroid.validate(Mode))
  329. return false;
  330. const std::vector<BenchmarkMeasure> ClusterCenterPoint =
  331. Centroid.getAsPoint();
  332. const std::vector<BenchmarkMeasure> SchedClassPoint =
  333. RSC.getAsPoint(Mode, STI, Centroid.getStats());
  334. if (SchedClassPoint.empty())
  335. return false; // In Uops mode validate() may not be enough.
  336. assert(ClusterCenterPoint.size() == SchedClassPoint.size() &&
  337. "Expected measured/sched data dimensions to match.");
  338. return Clustering.isNeighbour(ClusterCenterPoint, SchedClassPoint,
  339. AnalysisInconsistencyEpsilonSquared_);
  340. }
  341. void Analysis::printSchedClassDescHtml(const ResolvedSchedClass &RSC,
  342. raw_ostream &OS) const {
  343. OS << "<table class=\"sched-class-desc\">";
  344. OS << "<tr><th>Valid</th><th>Variant</th><th>NumMicroOps</th><th>Latency</"
  345. "th><th>RThroughput</th><th>WriteProcRes</th><th title=\"This is the "
  346. "idealized unit resource (port) pressure assuming ideal "
  347. "distribution\">Idealized Resource Pressure</th></tr>";
  348. if (RSC.SCDesc->isValid()) {
  349. const auto &SM = SubtargetInfo_->getSchedModel();
  350. OS << "<tr><td>&#10004;</td>";
  351. OS << "<td>" << (RSC.WasVariant ? "&#10004;" : "&#10005;") << "</td>";
  352. OS << "<td>" << RSC.SCDesc->NumMicroOps << "</td>";
  353. // Latencies.
  354. OS << "<td><ul>";
  355. for (int I = 0, E = RSC.SCDesc->NumWriteLatencyEntries; I < E; ++I) {
  356. const auto *const Entry =
  357. SubtargetInfo_->getWriteLatencyEntry(RSC.SCDesc, I);
  358. OS << "<li>" << Entry->Cycles;
  359. if (RSC.SCDesc->NumWriteLatencyEntries > 1) {
  360. // Dismabiguate if more than 1 latency.
  361. OS << " (WriteResourceID " << Entry->WriteResourceID << ")";
  362. }
  363. OS << "</li>";
  364. }
  365. OS << "</ul></td>";
  366. // inverse throughput.
  367. OS << "<td>";
  368. writeMeasurementValue<kEscapeHtml>(
  369. OS,
  370. MCSchedModel::getReciprocalThroughput(*SubtargetInfo_, *RSC.SCDesc));
  371. OS << "</td>";
  372. // WriteProcRes.
  373. OS << "<td><ul>";
  374. for (const auto &WPR : RSC.NonRedundantWriteProcRes) {
  375. OS << "<li><span class=\"mono\">";
  376. writeEscaped<kEscapeHtml>(OS,
  377. SM.getProcResource(WPR.ProcResourceIdx)->Name);
  378. OS << "</span>: " << WPR.Cycles << "</li>";
  379. }
  380. OS << "</ul></td>";
  381. // Idealized port pressure.
  382. OS << "<td><ul>";
  383. for (const auto &Pressure : RSC.IdealizedProcResPressure) {
  384. OS << "<li><span class=\"mono\">";
  385. writeEscaped<kEscapeHtml>(OS, SubtargetInfo_->getSchedModel()
  386. .getProcResource(Pressure.first)
  387. ->Name);
  388. OS << "</span>: ";
  389. writeMeasurementValue<kEscapeHtml>(OS, Pressure.second);
  390. OS << "</li>";
  391. }
  392. OS << "</ul></td>";
  393. OS << "</tr>";
  394. } else {
  395. OS << "<tr><td>&#10005;</td><td></td><td></td></tr>";
  396. }
  397. OS << "</table>";
  398. }
  399. void Analysis::printClusterRawHtml(
  400. const InstructionBenchmarkClustering::ClusterId &Id, StringRef display_name,
  401. llvm::raw_ostream &OS) const {
  402. const auto &Points = Clustering_.getPoints();
  403. const auto &Cluster = Clustering_.getCluster(Id);
  404. if (Cluster.PointIndices.empty())
  405. return;
  406. OS << "<div class=\"inconsistency\"><p>" << display_name << " Cluster ("
  407. << Cluster.PointIndices.size() << " points)</p>";
  408. OS << "<table class=\"sched-class-clusters\">";
  409. // Table Header.
  410. OS << "<tr><th>ClusterId</th><th>Opcode/Config</th>";
  411. for (const auto &Measurement : Points[Cluster.PointIndices[0]].Measurements) {
  412. OS << "<th>";
  413. writeEscaped<kEscapeHtml>(OS, Measurement.Key);
  414. OS << "</th>";
  415. }
  416. OS << "</tr>";
  417. // Point data.
  418. for (const auto &PointId : Cluster.PointIndices) {
  419. OS << "<tr class=\"bad-cluster\"><td>" << display_name << "</td><td><ul>";
  420. printPointHtml(Points[PointId], OS);
  421. OS << "</ul></td>";
  422. for (const auto &Measurement : Points[PointId].Measurements) {
  423. OS << "<td class=\"measurement\">";
  424. writeMeasurementValue<kEscapeHtml>(OS, Measurement.PerInstructionValue);
  425. }
  426. OS << "</tr>";
  427. }
  428. OS << "</table>";
  429. OS << "</div>";
  430. } // namespace exegesis
  431. static constexpr const char kHtmlHead[] = R"(
  432. <head>
  433. <title>llvm-exegesis Analysis Results</title>
  434. <style>
  435. body {
  436. font-family: sans-serif
  437. }
  438. span.sched-class-name {
  439. font-weight: bold;
  440. font-family: monospace;
  441. }
  442. span.opcode {
  443. font-family: monospace;
  444. }
  445. span.config {
  446. font-family: monospace;
  447. }
  448. div.inconsistency {
  449. margin-top: 50px;
  450. }
  451. table {
  452. margin-left: 50px;
  453. border-collapse: collapse;
  454. }
  455. table, table tr,td,th {
  456. border: 1px solid #444;
  457. }
  458. table ul {
  459. padding-left: 0px;
  460. margin: 0px;
  461. list-style-type: none;
  462. }
  463. table.sched-class-clusters td {
  464. padding-left: 10px;
  465. padding-right: 10px;
  466. padding-top: 10px;
  467. padding-bottom: 10px;
  468. }
  469. table.sched-class-desc td {
  470. padding-left: 10px;
  471. padding-right: 10px;
  472. padding-top: 2px;
  473. padding-bottom: 2px;
  474. }
  475. span.mono {
  476. font-family: monospace;
  477. }
  478. td.measurement {
  479. text-align: center;
  480. }
  481. tr.good-cluster td.measurement {
  482. color: #292
  483. }
  484. tr.bad-cluster td.measurement {
  485. color: #922
  486. }
  487. tr.good-cluster td.measurement span.minmax {
  488. color: #888;
  489. }
  490. tr.bad-cluster td.measurement span.minmax {
  491. color: #888;
  492. }
  493. </style>
  494. </head>
  495. )";
  496. template <>
  497. Error Analysis::run<Analysis::PrintSchedClassInconsistencies>(
  498. raw_ostream &OS) const {
  499. const auto &FirstPoint = Clustering_.getPoints()[0];
  500. // Print the header.
  501. OS << "<!DOCTYPE html><html>" << kHtmlHead << "<body>";
  502. OS << "<h1><span class=\"mono\">llvm-exegesis</span> Analysis Results</h1>";
  503. OS << "<h3>Triple: <span class=\"mono\">";
  504. writeEscaped<kEscapeHtml>(OS, FirstPoint.LLVMTriple);
  505. OS << "</span></h3><h3>Cpu: <span class=\"mono\">";
  506. writeEscaped<kEscapeHtml>(OS, FirstPoint.CpuName);
  507. OS << "</span></h3>";
  508. for (const auto &RSCAndPoints : makePointsPerSchedClass()) {
  509. if (!RSCAndPoints.RSC.SCDesc)
  510. continue;
  511. // Bucket sched class points into sched class clusters.
  512. std::vector<SchedClassCluster> SchedClassClusters;
  513. for (const size_t PointId : RSCAndPoints.PointIds) {
  514. const auto &ClusterId = Clustering_.getClusterIdForPoint(PointId);
  515. if (!ClusterId.isValid())
  516. continue; // Ignore noise and errors. FIXME: take noise into account ?
  517. if (ClusterId.isUnstable() ^ AnalysisDisplayUnstableOpcodes_)
  518. continue; // Either display stable or unstable clusters only.
  519. auto SchedClassClusterIt = llvm::find_if(
  520. SchedClassClusters, [ClusterId](const SchedClassCluster &C) {
  521. return C.id() == ClusterId;
  522. });
  523. if (SchedClassClusterIt == SchedClassClusters.end()) {
  524. SchedClassClusters.emplace_back();
  525. SchedClassClusterIt = std::prev(SchedClassClusters.end());
  526. }
  527. SchedClassClusterIt->addPoint(PointId, Clustering_);
  528. }
  529. // Print any scheduling class that has at least one cluster that does not
  530. // match the checked-in data.
  531. if (all_of(SchedClassClusters, [this,
  532. &RSCAndPoints](const SchedClassCluster &C) {
  533. return C.measurementsMatch(*SubtargetInfo_, RSCAndPoints.RSC,
  534. Clustering_,
  535. AnalysisInconsistencyEpsilonSquared_);
  536. }))
  537. continue; // Nothing weird.
  538. OS << "<div class=\"inconsistency\"><p>Sched Class <span "
  539. "class=\"sched-class-name\">";
  540. #if !defined(NDEBUG) || defined(LLVM_ENABLE_DUMP)
  541. writeEscaped<kEscapeHtml>(OS, RSCAndPoints.RSC.SCDesc->Name);
  542. #else
  543. OS << RSCAndPoints.RSC.SchedClassId;
  544. #endif
  545. OS << "</span> contains instructions whose performance characteristics do"
  546. " not match that of LLVM:</p>";
  547. printSchedClassClustersHtml(SchedClassClusters, RSCAndPoints.RSC, OS);
  548. OS << "<p>llvm SchedModel data:</p>";
  549. printSchedClassDescHtml(RSCAndPoints.RSC, OS);
  550. OS << "</div>";
  551. }
  552. printClusterRawHtml(InstructionBenchmarkClustering::ClusterId::noise(),
  553. "[noise]", OS);
  554. OS << "</body></html>";
  555. return Error::success();
  556. }
  557. } // namespace exegesis
  558. } // namespace llvm