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