Path: blob/main/contrib/llvm-project/llvm/tools/llvm-mca/Views/BottleneckAnalysis.cpp
35290 views
//===--------------------- BottleneckAnalysis.cpp ---------------*- C++ -*-===//1//2// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.3// See https://llvm.org/LICENSE.txt for license information.4// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception5//6//===----------------------------------------------------------------------===//7/// \file8///9/// This file implements the functionalities used by the BottleneckAnalysis10/// to report bottleneck info.11///12//===----------------------------------------------------------------------===//1314#include "Views/BottleneckAnalysis.h"15#include "llvm/MC/MCInst.h"16#include "llvm/MCA/Support.h"17#include "llvm/Support/Format.h"1819namespace llvm {20namespace mca {2122#define DEBUG_TYPE "llvm-mca"2324PressureTracker::PressureTracker(const MCSchedModel &Model)25: SM(Model),26ResourcePressureDistribution(Model.getNumProcResourceKinds(), 0),27ProcResID2Mask(Model.getNumProcResourceKinds(), 0),28ResIdx2ProcResID(Model.getNumProcResourceKinds(), 0),29ProcResID2ResourceUsersIndex(Model.getNumProcResourceKinds(), 0) {30computeProcResourceMasks(SM, ProcResID2Mask);3132// Ignore the invalid resource at index zero.33unsigned NextResourceUsersIdx = 0;34for (unsigned I = 1, E = Model.getNumProcResourceKinds(); I < E; ++I) {35const MCProcResourceDesc &ProcResource = *SM.getProcResource(I);36ProcResID2ResourceUsersIndex[I] = NextResourceUsersIdx;37NextResourceUsersIdx += ProcResource.NumUnits;38uint64_t ResourceMask = ProcResID2Mask[I];39ResIdx2ProcResID[getResourceStateIndex(ResourceMask)] = I;40}4142ResourceUsers.resize(NextResourceUsersIdx);43std::fill(ResourceUsers.begin(), ResourceUsers.end(),44std::make_pair<unsigned, unsigned>(~0U, 0U));45}4647void PressureTracker::getResourceUsers(uint64_t ResourceMask,48SmallVectorImpl<User> &Users) const {49unsigned Index = getResourceStateIndex(ResourceMask);50unsigned ProcResID = ResIdx2ProcResID[Index];51const MCProcResourceDesc &PRDesc = *SM.getProcResource(ProcResID);52for (unsigned I = 0, E = PRDesc.NumUnits; I < E; ++I) {53const User U = getResourceUser(ProcResID, I);54if (U.second && IPI.contains(U.first))55Users.emplace_back(U);56}57}5859void PressureTracker::onInstructionDispatched(unsigned IID) {60IPI.insert(std::make_pair(IID, InstructionPressureInfo()));61}6263void PressureTracker::onInstructionExecuted(unsigned IID) { IPI.erase(IID); }6465void PressureTracker::handleInstructionIssuedEvent(66const HWInstructionIssuedEvent &Event) {67unsigned IID = Event.IR.getSourceIndex();68for (const ResourceUse &Use : Event.UsedResources) {69const ResourceRef &RR = Use.first;70unsigned Index = ProcResID2ResourceUsersIndex[RR.first];71Index += llvm::countr_zero(RR.second);72ResourceUsers[Index] = std::make_pair(IID, Use.second.getNumerator());73}74}7576void PressureTracker::updateResourcePressureDistribution(77uint64_t CumulativeMask) {78while (CumulativeMask) {79uint64_t Current = CumulativeMask & (-CumulativeMask);80unsigned ResIdx = getResourceStateIndex(Current);81unsigned ProcResID = ResIdx2ProcResID[ResIdx];82uint64_t Mask = ProcResID2Mask[ProcResID];8384if (Mask == Current) {85ResourcePressureDistribution[ProcResID]++;86CumulativeMask ^= Current;87continue;88}8990Mask ^= Current;91while (Mask) {92uint64_t SubUnit = Mask & (-Mask);93ResIdx = getResourceStateIndex(SubUnit);94ProcResID = ResIdx2ProcResID[ResIdx];95ResourcePressureDistribution[ProcResID]++;96Mask ^= SubUnit;97}9899CumulativeMask ^= Current;100}101}102103void PressureTracker::handlePressureEvent(const HWPressureEvent &Event) {104assert(Event.Reason != HWPressureEvent::INVALID &&105"Unexpected invalid event!");106107switch (Event.Reason) {108default:109break;110111case HWPressureEvent::RESOURCES: {112const uint64_t ResourceMask = Event.ResourceMask;113updateResourcePressureDistribution(Event.ResourceMask);114115for (const InstRef &IR : Event.AffectedInstructions) {116const Instruction &IS = *IR.getInstruction();117unsigned BusyResources = IS.getCriticalResourceMask() & ResourceMask;118if (!BusyResources)119continue;120121unsigned IID = IR.getSourceIndex();122IPI[IID].ResourcePressureCycles++;123}124break;125}126127case HWPressureEvent::REGISTER_DEPS:128for (const InstRef &IR : Event.AffectedInstructions) {129unsigned IID = IR.getSourceIndex();130IPI[IID].RegisterPressureCycles++;131}132break;133134case HWPressureEvent::MEMORY_DEPS:135for (const InstRef &IR : Event.AffectedInstructions) {136unsigned IID = IR.getSourceIndex();137IPI[IID].MemoryPressureCycles++;138}139}140}141142#ifndef NDEBUG143void DependencyGraph::dumpDependencyEdge(raw_ostream &OS,144const DependencyEdge &DepEdge,145MCInstPrinter &MCIP) const {146unsigned FromIID = DepEdge.FromIID;147unsigned ToIID = DepEdge.ToIID;148assert(FromIID < ToIID && "Graph should be acyclic!");149150const DependencyEdge::Dependency &DE = DepEdge.Dep;151assert(DE.Type != DependencyEdge::DT_INVALID && "Unexpected invalid edge!");152153OS << " FROM: " << FromIID << " TO: " << ToIID << " ";154if (DE.Type == DependencyEdge::DT_REGISTER) {155OS << " - REGISTER: ";156MCIP.printRegName(OS, DE.ResourceOrRegID);157} else if (DE.Type == DependencyEdge::DT_MEMORY) {158OS << " - MEMORY";159} else {160assert(DE.Type == DependencyEdge::DT_RESOURCE &&161"Unsupported dependency type!");162OS << " - RESOURCE MASK: " << DE.ResourceOrRegID;163}164OS << " - COST: " << DE.Cost << '\n';165}166#endif // NDEBUG167168void DependencyGraph::pruneEdges(unsigned Iterations) {169for (DGNode &N : Nodes) {170unsigned NumPruned = 0;171const unsigned Size = N.OutgoingEdges.size();172// Use a cut-off threshold to prune edges with a low frequency.173for (unsigned I = 0, E = Size; I < E; ++I) {174DependencyEdge &Edge = N.OutgoingEdges[I];175if (Edge.Frequency == Iterations)176continue;177double Factor = (double)Edge.Frequency / Iterations;178if (0.10 < Factor)179continue;180Nodes[Edge.ToIID].NumPredecessors--;181std::swap(Edge, N.OutgoingEdges[E - 1]);182--E;183++NumPruned;184}185186if (NumPruned)187N.OutgoingEdges.resize(Size - NumPruned);188}189}190191void DependencyGraph::initializeRootSet(192SmallVectorImpl<unsigned> &RootSet) const {193for (unsigned I = 0, E = Nodes.size(); I < E; ++I) {194const DGNode &N = Nodes[I];195if (N.NumPredecessors == 0 && !N.OutgoingEdges.empty())196RootSet.emplace_back(I);197}198}199200void DependencyGraph::propagateThroughEdges(SmallVectorImpl<unsigned> &RootSet,201unsigned Iterations) {202SmallVector<unsigned, 8> ToVisit;203204// A critical sequence is computed as the longest path from a node of the205// RootSet to a leaf node (i.e. a node with no successors). The RootSet is206// composed of nodes with at least one successor, and no predecessors.207//208// Each node of the graph starts with an initial default cost of zero. The209// cost of a node is a measure of criticality: the higher the cost, the bigger210// is the performance impact.211// For register and memory dependencies, the cost is a function of the write212// latency as well as the actual delay (in cycles) caused to users.213// For processor resource dependencies, the cost is a function of the resource214// pressure. Resource interferences with low frequency values are ignored.215//216// This algorithm is very similar to a (reverse) Dijkstra. Every iteration of217// the inner loop selects (i.e. visits) a node N from a set of `unvisited218// nodes`, and then propagates the cost of N to all its neighbors.219//220// The `unvisited nodes` set initially contains all the nodes from the221// RootSet. A node N is added to the `unvisited nodes` if all its222// predecessors have been visited already.223//224// For simplicity, every node tracks the number of unvisited incoming edges in225// field `NumVisitedPredecessors`. When the value of that field drops to226// zero, then the corresponding node is added to a `ToVisit` set.227//228// At the end of every iteration of the outer loop, set `ToVisit` becomes our229// new `unvisited nodes` set.230//231// The algorithm terminates when the set of unvisited nodes (i.e. our RootSet)232// is empty. This algorithm works under the assumption that the graph is233// acyclic.234do {235for (unsigned IID : RootSet) {236const DGNode &N = Nodes[IID];237for (const DependencyEdge &DepEdge : N.OutgoingEdges) {238unsigned ToIID = DepEdge.ToIID;239DGNode &To = Nodes[ToIID];240uint64_t Cost = N.Cost + DepEdge.Dep.Cost;241// Check if this is the most expensive incoming edge seen so far. In242// case, update the total cost of the destination node (ToIID), as well243// its field `CriticalPredecessor`.244if (Cost > To.Cost) {245To.CriticalPredecessor = DepEdge;246To.Cost = Cost;247To.Depth = N.Depth + 1;248}249To.NumVisitedPredecessors++;250if (To.NumVisitedPredecessors == To.NumPredecessors)251ToVisit.emplace_back(ToIID);252}253}254255std::swap(RootSet, ToVisit);256ToVisit.clear();257} while (!RootSet.empty());258}259260void DependencyGraph::getCriticalSequence(261SmallVectorImpl<const DependencyEdge *> &Seq) const {262// At this stage, nodes of the graph have been already visited, and costs have263// been propagated through the edges (see method `propagateThroughEdges()`).264265// Identify the node N with the highest cost in the graph. By construction,266// that node is the last instruction of our critical sequence.267// Field N.Depth would tell us the total length of the sequence.268//269// To obtain the sequence of critical edges, we simply follow the chain of270// critical predecessors starting from node N (field271// DGNode::CriticalPredecessor).272const auto It =273llvm::max_element(Nodes, [](const DGNode &Lhs, const DGNode &Rhs) {274return Lhs.Cost < Rhs.Cost;275});276unsigned IID = std::distance(Nodes.begin(), It);277Seq.resize(Nodes[IID].Depth);278for (const DependencyEdge *&DE : llvm::reverse(Seq)) {279const DGNode &N = Nodes[IID];280DE = &N.CriticalPredecessor;281IID = N.CriticalPredecessor.FromIID;282}283}284285void BottleneckAnalysis::printInstruction(formatted_raw_ostream &FOS,286const MCInst &MCI,287bool UseDifferentColor) const {288FOS.PadToColumn(14);289if (UseDifferentColor)290FOS.changeColor(raw_ostream::CYAN, true, false);291FOS << printInstructionString(MCI);292if (UseDifferentColor)293FOS.resetColor();294}295296void BottleneckAnalysis::printCriticalSequence(raw_ostream &OS) const {297// Early exit if no bottlenecks were found during the simulation.298if (!SeenStallCycles || !BPI.PressureIncreaseCycles)299return;300301SmallVector<const DependencyEdge *, 16> Seq;302DG.getCriticalSequence(Seq);303if (Seq.empty())304return;305306OS << "\nCritical sequence based on the simulation:\n\n";307308const DependencyEdge &FirstEdge = *Seq[0];309ArrayRef<llvm::MCInst> Source = getSource();310unsigned FromIID = FirstEdge.FromIID % Source.size();311unsigned ToIID = FirstEdge.ToIID % Source.size();312bool IsLoopCarried = FromIID >= ToIID;313314formatted_raw_ostream FOS(OS);315FOS.PadToColumn(14);316FOS << "Instruction";317FOS.PadToColumn(58);318FOS << "Dependency Information";319320bool HasColors = FOS.has_colors();321322unsigned CurrentIID = 0;323if (IsLoopCarried) {324FOS << "\n +----< " << FromIID << ".";325printInstruction(FOS, Source[FromIID], HasColors);326FOS << "\n |\n | < loop carried > \n |";327} else {328while (CurrentIID < FromIID) {329FOS << "\n " << CurrentIID << ".";330printInstruction(FOS, Source[CurrentIID]);331CurrentIID++;332}333334FOS << "\n +----< " << CurrentIID << ".";335printInstruction(FOS, Source[CurrentIID], HasColors);336CurrentIID++;337}338339for (const DependencyEdge *&DE : Seq) {340ToIID = DE->ToIID % Source.size();341unsigned LastIID = CurrentIID > ToIID ? Source.size() : ToIID;342343while (CurrentIID < LastIID) {344FOS << "\n | " << CurrentIID << ".";345printInstruction(FOS, Source[CurrentIID]);346CurrentIID++;347}348349if (CurrentIID == ToIID) {350FOS << "\n +----> " << ToIID << ".";351printInstruction(FOS, Source[CurrentIID], HasColors);352} else {353FOS << "\n |\n | < loop carried > \n |"354<< "\n +----> " << ToIID << ".";355printInstruction(FOS, Source[ToIID], HasColors);356}357FOS.PadToColumn(58);358359const DependencyEdge::Dependency &Dep = DE->Dep;360if (HasColors)361FOS.changeColor(raw_ostream::SAVEDCOLOR, true, false);362363if (Dep.Type == DependencyEdge::DT_REGISTER) {364FOS << "## REGISTER dependency: ";365if (HasColors)366FOS.changeColor(raw_ostream::MAGENTA, true, false);367getInstPrinter().printRegName(FOS, Dep.ResourceOrRegID);368} else if (Dep.Type == DependencyEdge::DT_MEMORY) {369FOS << "## MEMORY dependency.";370} else {371assert(Dep.Type == DependencyEdge::DT_RESOURCE &&372"Unsupported dependency type!");373FOS << "## RESOURCE interference: ";374if (HasColors)375FOS.changeColor(raw_ostream::MAGENTA, true, false);376FOS << Tracker.resolveResourceName(Dep.ResourceOrRegID);377if (HasColors) {378FOS.resetColor();379FOS.changeColor(raw_ostream::SAVEDCOLOR, true, false);380}381FOS << " [ probability: " << ((DE->Frequency * 100) / Iterations)382<< "% ]";383}384if (HasColors)385FOS.resetColor();386++CurrentIID;387}388389while (CurrentIID < Source.size()) {390FOS << "\n " << CurrentIID << ".";391printInstruction(FOS, Source[CurrentIID]);392CurrentIID++;393}394395FOS << '\n';396FOS.flush();397}398399#ifndef NDEBUG400void DependencyGraph::dump(raw_ostream &OS, MCInstPrinter &MCIP) const {401OS << "\nREG DEPS\n";402for (const DGNode &Node : Nodes)403for (const DependencyEdge &DE : Node.OutgoingEdges)404if (DE.Dep.Type == DependencyEdge::DT_REGISTER)405dumpDependencyEdge(OS, DE, MCIP);406407OS << "\nMEM DEPS\n";408for (const DGNode &Node : Nodes)409for (const DependencyEdge &DE : Node.OutgoingEdges)410if (DE.Dep.Type == DependencyEdge::DT_MEMORY)411dumpDependencyEdge(OS, DE, MCIP);412413OS << "\nRESOURCE DEPS\n";414for (const DGNode &Node : Nodes)415for (const DependencyEdge &DE : Node.OutgoingEdges)416if (DE.Dep.Type == DependencyEdge::DT_RESOURCE)417dumpDependencyEdge(OS, DE, MCIP);418}419#endif // NDEBUG420421void DependencyGraph::addDependency(unsigned From, unsigned To,422DependencyEdge::Dependency &&Dep) {423DGNode &NodeFrom = Nodes[From];424DGNode &NodeTo = Nodes[To];425SmallVectorImpl<DependencyEdge> &Vec = NodeFrom.OutgoingEdges;426427auto It = find_if(Vec, [To, Dep](DependencyEdge &DE) {428return DE.ToIID == To && DE.Dep.ResourceOrRegID == Dep.ResourceOrRegID;429});430431if (It != Vec.end()) {432It->Dep.Cost += Dep.Cost;433It->Frequency++;434return;435}436437DependencyEdge DE = {Dep, From, To, 1};438Vec.emplace_back(DE);439NodeTo.NumPredecessors++;440}441442BottleneckAnalysis::BottleneckAnalysis(const MCSubtargetInfo &sti,443MCInstPrinter &Printer,444ArrayRef<MCInst> S, unsigned NumIter)445: InstructionView(sti, Printer, S), Tracker(sti.getSchedModel()),446DG(S.size() * 3), Iterations(NumIter), TotalCycles(0),447PressureIncreasedBecauseOfResources(false),448PressureIncreasedBecauseOfRegisterDependencies(false),449PressureIncreasedBecauseOfMemoryDependencies(false),450SeenStallCycles(false), BPI() {}451452void BottleneckAnalysis::addRegisterDep(unsigned From, unsigned To,453unsigned RegID, unsigned Cost) {454bool IsLoopCarried = From >= To;455unsigned SourceSize = getSource().size();456if (IsLoopCarried) {457DG.addRegisterDep(From, To + SourceSize, RegID, Cost);458DG.addRegisterDep(From + SourceSize, To + (SourceSize * 2), RegID, Cost);459return;460}461DG.addRegisterDep(From + SourceSize, To + SourceSize, RegID, Cost);462}463464void BottleneckAnalysis::addMemoryDep(unsigned From, unsigned To,465unsigned Cost) {466bool IsLoopCarried = From >= To;467unsigned SourceSize = getSource().size();468if (IsLoopCarried) {469DG.addMemoryDep(From, To + SourceSize, Cost);470DG.addMemoryDep(From + SourceSize, To + (SourceSize * 2), Cost);471return;472}473DG.addMemoryDep(From + SourceSize, To + SourceSize, Cost);474}475476void BottleneckAnalysis::addResourceDep(unsigned From, unsigned To,477uint64_t Mask, unsigned Cost) {478bool IsLoopCarried = From >= To;479unsigned SourceSize = getSource().size();480if (IsLoopCarried) {481DG.addResourceDep(From, To + SourceSize, Mask, Cost);482DG.addResourceDep(From + SourceSize, To + (SourceSize * 2), Mask, Cost);483return;484}485DG.addResourceDep(From + SourceSize, To + SourceSize, Mask, Cost);486}487488void BottleneckAnalysis::onEvent(const HWInstructionEvent &Event) {489const unsigned IID = Event.IR.getSourceIndex();490if (Event.Type == HWInstructionEvent::Dispatched) {491Tracker.onInstructionDispatched(IID);492return;493}494if (Event.Type == HWInstructionEvent::Executed) {495Tracker.onInstructionExecuted(IID);496return;497}498499if (Event.Type != HWInstructionEvent::Issued)500return;501502ArrayRef<llvm::MCInst> Source = getSource();503const Instruction &IS = *Event.IR.getInstruction();504unsigned To = IID % Source.size();505506unsigned Cycles = 2 * Tracker.getResourcePressureCycles(IID);507uint64_t ResourceMask = IS.getCriticalResourceMask();508SmallVector<std::pair<unsigned, unsigned>, 4> Users;509while (ResourceMask) {510uint64_t Current = ResourceMask & (-ResourceMask);511Tracker.getResourceUsers(Current, Users);512for (const std::pair<unsigned, unsigned> &U : Users)513addResourceDep(U.first % Source.size(), To, Current, U.second + Cycles);514Users.clear();515ResourceMask ^= Current;516}517518const CriticalDependency &RegDep = IS.getCriticalRegDep();519if (RegDep.Cycles) {520Cycles = RegDep.Cycles + 2 * Tracker.getRegisterPressureCycles(IID);521unsigned From = RegDep.IID % Source.size();522addRegisterDep(From, To, RegDep.RegID, Cycles);523}524525const CriticalDependency &MemDep = IS.getCriticalMemDep();526if (MemDep.Cycles) {527Cycles = MemDep.Cycles + 2 * Tracker.getMemoryPressureCycles(IID);528unsigned From = MemDep.IID % Source.size();529addMemoryDep(From, To, Cycles);530}531532Tracker.handleInstructionIssuedEvent(533static_cast<const HWInstructionIssuedEvent &>(Event));534535// Check if this is the last simulated instruction.536if (IID == ((Iterations * Source.size()) - 1))537DG.finalizeGraph(Iterations);538}539540void BottleneckAnalysis::onEvent(const HWPressureEvent &Event) {541assert(Event.Reason != HWPressureEvent::INVALID &&542"Unexpected invalid event!");543544Tracker.handlePressureEvent(Event);545546switch (Event.Reason) {547default:548break;549550case HWPressureEvent::RESOURCES:551PressureIncreasedBecauseOfResources = true;552break;553case HWPressureEvent::REGISTER_DEPS:554PressureIncreasedBecauseOfRegisterDependencies = true;555break;556case HWPressureEvent::MEMORY_DEPS:557PressureIncreasedBecauseOfMemoryDependencies = true;558break;559}560}561562void BottleneckAnalysis::onCycleEnd() {563++TotalCycles;564565bool PressureIncreasedBecauseOfDataDependencies =566PressureIncreasedBecauseOfRegisterDependencies ||567PressureIncreasedBecauseOfMemoryDependencies;568if (!PressureIncreasedBecauseOfResources &&569!PressureIncreasedBecauseOfDataDependencies)570return;571572++BPI.PressureIncreaseCycles;573if (PressureIncreasedBecauseOfRegisterDependencies)574++BPI.RegisterDependencyCycles;575if (PressureIncreasedBecauseOfMemoryDependencies)576++BPI.MemoryDependencyCycles;577if (PressureIncreasedBecauseOfDataDependencies)578++BPI.DataDependencyCycles;579if (PressureIncreasedBecauseOfResources)580++BPI.ResourcePressureCycles;581PressureIncreasedBecauseOfResources = false;582PressureIncreasedBecauseOfRegisterDependencies = false;583PressureIncreasedBecauseOfMemoryDependencies = false;584}585586void BottleneckAnalysis::printBottleneckHints(raw_ostream &OS) const {587if (!SeenStallCycles || !BPI.PressureIncreaseCycles) {588OS << "\n\nNo resource or data dependency bottlenecks discovered.\n";589return;590}591592double PressurePerCycle =593(double)BPI.PressureIncreaseCycles * 100 / TotalCycles;594double ResourcePressurePerCycle =595(double)BPI.ResourcePressureCycles * 100 / TotalCycles;596double DDPerCycle = (double)BPI.DataDependencyCycles * 100 / TotalCycles;597double RegDepPressurePerCycle =598(double)BPI.RegisterDependencyCycles * 100 / TotalCycles;599double MemDepPressurePerCycle =600(double)BPI.MemoryDependencyCycles * 100 / TotalCycles;601602OS << "\n\nCycles with backend pressure increase [ "603<< format("%.2f", floor((PressurePerCycle * 100) + 0.5) / 100) << "% ]";604605OS << "\nThroughput Bottlenecks: "606<< "\n Resource Pressure [ "607<< format("%.2f", floor((ResourcePressurePerCycle * 100) + 0.5) / 100)608<< "% ]";609610if (BPI.PressureIncreaseCycles) {611ArrayRef<unsigned> Distribution = Tracker.getResourcePressureDistribution();612const MCSchedModel &SM = getSubTargetInfo().getSchedModel();613for (unsigned I = 0, E = Distribution.size(); I < E; ++I) {614unsigned ReleaseAtCycles = Distribution[I];615if (ReleaseAtCycles) {616double Frequency = (double)ReleaseAtCycles * 100 / TotalCycles;617const MCProcResourceDesc &PRDesc = *SM.getProcResource(I);618OS << "\n - " << PRDesc.Name << " [ "619<< format("%.2f", floor((Frequency * 100) + 0.5) / 100) << "% ]";620}621}622}623624OS << "\n Data Dependencies: [ "625<< format("%.2f", floor((DDPerCycle * 100) + 0.5) / 100) << "% ]";626OS << "\n - Register Dependencies [ "627<< format("%.2f", floor((RegDepPressurePerCycle * 100) + 0.5) / 100)628<< "% ]";629OS << "\n - Memory Dependencies [ "630<< format("%.2f", floor((MemDepPressurePerCycle * 100) + 0.5) / 100)631<< "% ]\n";632}633634void BottleneckAnalysis::printView(raw_ostream &OS) const {635std::string Buffer;636raw_string_ostream TempStream(Buffer);637printBottleneckHints(TempStream);638TempStream.flush();639OS << Buffer;640printCriticalSequence(OS);641}642643} // namespace mca.644} // namespace llvm645646647