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