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