10b57cec5SDimitry Andric //===--------------------- TimelineView.cpp ---------------------*- C++ -*-===// 20b57cec5SDimitry Andric // 30b57cec5SDimitry Andric // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. 40b57cec5SDimitry Andric // See https://llvm.org/LICENSE.txt for license information. 50b57cec5SDimitry Andric // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception 60b57cec5SDimitry Andric // 70b57cec5SDimitry Andric //===----------------------------------------------------------------------===// 80b57cec5SDimitry Andric /// \brief 90b57cec5SDimitry Andric /// 100b57cec5SDimitry Andric /// This file implements the TimelineView interface. 110b57cec5SDimitry Andric /// 120b57cec5SDimitry Andric //===----------------------------------------------------------------------===// 130b57cec5SDimitry Andric 140b57cec5SDimitry Andric #include "Views/TimelineView.h" 158bcb0991SDimitry Andric #include <numeric> 160b57cec5SDimitry Andric 170b57cec5SDimitry Andric namespace llvm { 180b57cec5SDimitry Andric namespace mca { 190b57cec5SDimitry Andric 200b57cec5SDimitry Andric TimelineView::TimelineView(const MCSubtargetInfo &sti, MCInstPrinter &Printer, 210b57cec5SDimitry Andric llvm::ArrayRef<llvm::MCInst> S, unsigned Iterations, 220b57cec5SDimitry Andric unsigned Cycles) 23e8d8bef9SDimitry Andric : InstructionView(sti, Printer, S), CurrentCycle(0), 24*fe6060f1SDimitry Andric MaxCycle(Cycles == 0 ? std::numeric_limits<unsigned>::max() : Cycles), 25*fe6060f1SDimitry Andric LastCycle(0), WaitTime(S.size()), UsedBuffer(S.size()) { 26e8d8bef9SDimitry Andric unsigned NumInstructions = getSource().size(); 270b57cec5SDimitry Andric assert(Iterations && "Invalid number of iterations specified!"); 280b57cec5SDimitry Andric NumInstructions *= Iterations; 290b57cec5SDimitry Andric Timeline.resize(NumInstructions); 300b57cec5SDimitry Andric TimelineViewEntry InvalidTVEntry = {-1, 0, 0, 0, 0}; 310b57cec5SDimitry Andric std::fill(Timeline.begin(), Timeline.end(), InvalidTVEntry); 320b57cec5SDimitry Andric 330b57cec5SDimitry Andric WaitTimeEntry NullWTEntry = {0, 0, 0}; 340b57cec5SDimitry Andric std::fill(WaitTime.begin(), WaitTime.end(), NullWTEntry); 350b57cec5SDimitry Andric 360b57cec5SDimitry Andric std::pair<unsigned, int> NullUsedBufferEntry = {/* Invalid resource ID*/ 0, 370b57cec5SDimitry Andric /* unknown buffer size */ -1}; 380b57cec5SDimitry Andric std::fill(UsedBuffer.begin(), UsedBuffer.end(), NullUsedBufferEntry); 390b57cec5SDimitry Andric } 400b57cec5SDimitry Andric 410b57cec5SDimitry Andric void TimelineView::onReservedBuffers(const InstRef &IR, 420b57cec5SDimitry Andric ArrayRef<unsigned> Buffers) { 43e8d8bef9SDimitry Andric if (IR.getSourceIndex() >= getSource().size()) 440b57cec5SDimitry Andric return; 450b57cec5SDimitry Andric 46e8d8bef9SDimitry Andric const MCSchedModel &SM = getSubTargetInfo().getSchedModel(); 470b57cec5SDimitry Andric std::pair<unsigned, int> BufferInfo = {0, -1}; 480b57cec5SDimitry Andric for (const unsigned Buffer : Buffers) { 490b57cec5SDimitry Andric const MCProcResourceDesc &MCDesc = *SM.getProcResource(Buffer); 500b57cec5SDimitry Andric if (!BufferInfo.first || BufferInfo.second > MCDesc.BufferSize) { 510b57cec5SDimitry Andric BufferInfo.first = Buffer; 520b57cec5SDimitry Andric BufferInfo.second = MCDesc.BufferSize; 530b57cec5SDimitry Andric } 540b57cec5SDimitry Andric } 550b57cec5SDimitry Andric 560b57cec5SDimitry Andric UsedBuffer[IR.getSourceIndex()] = BufferInfo; 570b57cec5SDimitry Andric } 580b57cec5SDimitry Andric 590b57cec5SDimitry Andric void TimelineView::onEvent(const HWInstructionEvent &Event) { 600b57cec5SDimitry Andric const unsigned Index = Event.IR.getSourceIndex(); 610b57cec5SDimitry Andric if (Index >= Timeline.size()) 620b57cec5SDimitry Andric return; 630b57cec5SDimitry Andric 640b57cec5SDimitry Andric switch (Event.Type) { 650b57cec5SDimitry Andric case HWInstructionEvent::Retired: { 660b57cec5SDimitry Andric TimelineViewEntry &TVEntry = Timeline[Index]; 670b57cec5SDimitry Andric if (CurrentCycle < MaxCycle) 680b57cec5SDimitry Andric TVEntry.CycleRetired = CurrentCycle; 690b57cec5SDimitry Andric 700b57cec5SDimitry Andric // Update the WaitTime entry which corresponds to this Index. 710b57cec5SDimitry Andric assert(TVEntry.CycleDispatched >= 0 && "Invalid TVEntry found!"); 720b57cec5SDimitry Andric unsigned CycleDispatched = static_cast<unsigned>(TVEntry.CycleDispatched); 73e8d8bef9SDimitry Andric WaitTimeEntry &WTEntry = WaitTime[Index % getSource().size()]; 740b57cec5SDimitry Andric WTEntry.CyclesSpentInSchedulerQueue += 750b57cec5SDimitry Andric TVEntry.CycleIssued - CycleDispatched; 760b57cec5SDimitry Andric assert(CycleDispatched <= TVEntry.CycleReady && 770b57cec5SDimitry Andric "Instruction cannot be ready if it hasn't been dispatched yet!"); 780b57cec5SDimitry Andric WTEntry.CyclesSpentInSQWhileReady += 790b57cec5SDimitry Andric TVEntry.CycleIssued - TVEntry.CycleReady; 80*fe6060f1SDimitry Andric if (CurrentCycle > TVEntry.CycleExecuted) { 810b57cec5SDimitry Andric WTEntry.CyclesSpentAfterWBAndBeforeRetire += 820b57cec5SDimitry Andric (CurrentCycle - 1) - TVEntry.CycleExecuted; 83*fe6060f1SDimitry Andric } 840b57cec5SDimitry Andric break; 850b57cec5SDimitry Andric } 860b57cec5SDimitry Andric case HWInstructionEvent::Ready: 870b57cec5SDimitry Andric Timeline[Index].CycleReady = CurrentCycle; 880b57cec5SDimitry Andric break; 890b57cec5SDimitry Andric case HWInstructionEvent::Issued: 900b57cec5SDimitry Andric Timeline[Index].CycleIssued = CurrentCycle; 910b57cec5SDimitry Andric break; 920b57cec5SDimitry Andric case HWInstructionEvent::Executed: 930b57cec5SDimitry Andric Timeline[Index].CycleExecuted = CurrentCycle; 940b57cec5SDimitry Andric break; 950b57cec5SDimitry Andric case HWInstructionEvent::Dispatched: 960b57cec5SDimitry Andric // There may be multiple dispatch events. Microcoded instructions that are 970b57cec5SDimitry Andric // expanded into multiple uOps may require multiple dispatch cycles. Here, 980b57cec5SDimitry Andric // we want to capture the first dispatch cycle. 990b57cec5SDimitry Andric if (Timeline[Index].CycleDispatched == -1) 1000b57cec5SDimitry Andric Timeline[Index].CycleDispatched = static_cast<int>(CurrentCycle); 1010b57cec5SDimitry Andric break; 1020b57cec5SDimitry Andric default: 1030b57cec5SDimitry Andric return; 1040b57cec5SDimitry Andric } 1050b57cec5SDimitry Andric if (CurrentCycle < MaxCycle) 1060b57cec5SDimitry Andric LastCycle = std::max(LastCycle, CurrentCycle); 1070b57cec5SDimitry Andric } 1080b57cec5SDimitry Andric 1090b57cec5SDimitry Andric static raw_ostream::Colors chooseColor(unsigned CumulativeCycles, 1100b57cec5SDimitry Andric unsigned Executions, int BufferSize) { 1110b57cec5SDimitry Andric if (CumulativeCycles && BufferSize < 0) 1120b57cec5SDimitry Andric return raw_ostream::MAGENTA; 1130b57cec5SDimitry Andric unsigned Size = static_cast<unsigned>(BufferSize); 1140b57cec5SDimitry Andric if (CumulativeCycles >= Size * Executions) 1150b57cec5SDimitry Andric return raw_ostream::RED; 1160b57cec5SDimitry Andric if ((CumulativeCycles * 2) >= Size * Executions) 1170b57cec5SDimitry Andric return raw_ostream::YELLOW; 1180b57cec5SDimitry Andric return raw_ostream::SAVEDCOLOR; 1190b57cec5SDimitry Andric } 1200b57cec5SDimitry Andric 1210b57cec5SDimitry Andric static void tryChangeColor(raw_ostream &OS, unsigned Cycles, 1220b57cec5SDimitry Andric unsigned Executions, int BufferSize) { 1230b57cec5SDimitry Andric if (!OS.has_colors()) 1240b57cec5SDimitry Andric return; 1250b57cec5SDimitry Andric 1260b57cec5SDimitry Andric raw_ostream::Colors Color = chooseColor(Cycles, Executions, BufferSize); 1270b57cec5SDimitry Andric if (Color == raw_ostream::SAVEDCOLOR) { 1280b57cec5SDimitry Andric OS.resetColor(); 1290b57cec5SDimitry Andric return; 1300b57cec5SDimitry Andric } 1310b57cec5SDimitry Andric OS.changeColor(Color, /* bold */ true, /* BG */ false); 1320b57cec5SDimitry Andric } 1330b57cec5SDimitry Andric 1340b57cec5SDimitry Andric void TimelineView::printWaitTimeEntry(formatted_raw_ostream &OS, 1350b57cec5SDimitry Andric const WaitTimeEntry &Entry, 1360b57cec5SDimitry Andric unsigned SourceIndex, 1370b57cec5SDimitry Andric unsigned Executions) const { 138e8d8bef9SDimitry Andric bool PrintingTotals = SourceIndex == getSource().size(); 1398bcb0991SDimitry Andric unsigned CumulativeExecutions = PrintingTotals ? Timeline.size() : Executions; 1408bcb0991SDimitry Andric 1418bcb0991SDimitry Andric if (!PrintingTotals) 1420b57cec5SDimitry Andric OS << SourceIndex << '.'; 1438bcb0991SDimitry Andric 1440b57cec5SDimitry Andric OS.PadToColumn(7); 1450b57cec5SDimitry Andric 1460b57cec5SDimitry Andric double AverageTime1, AverageTime2, AverageTime3; 1478bcb0991SDimitry Andric AverageTime1 = 1488bcb0991SDimitry Andric (double)Entry.CyclesSpentInSchedulerQueue / CumulativeExecutions; 1498bcb0991SDimitry Andric AverageTime2 = (double)Entry.CyclesSpentInSQWhileReady / CumulativeExecutions; 1508bcb0991SDimitry Andric AverageTime3 = 1518bcb0991SDimitry Andric (double)Entry.CyclesSpentAfterWBAndBeforeRetire / CumulativeExecutions; 1520b57cec5SDimitry Andric 1530b57cec5SDimitry Andric OS << Executions; 1540b57cec5SDimitry Andric OS.PadToColumn(13); 1558bcb0991SDimitry Andric 1568bcb0991SDimitry Andric int BufferSize = PrintingTotals ? 0 : UsedBuffer[SourceIndex].second; 1578bcb0991SDimitry Andric if (!PrintingTotals) 1588bcb0991SDimitry Andric tryChangeColor(OS, Entry.CyclesSpentInSchedulerQueue, CumulativeExecutions, 1598bcb0991SDimitry Andric BufferSize); 1600b57cec5SDimitry Andric OS << format("%.1f", floor((AverageTime1 * 10) + 0.5) / 10); 1610b57cec5SDimitry Andric OS.PadToColumn(20); 1628bcb0991SDimitry Andric if (!PrintingTotals) 1638bcb0991SDimitry Andric tryChangeColor(OS, Entry.CyclesSpentInSQWhileReady, CumulativeExecutions, 1648bcb0991SDimitry Andric BufferSize); 1650b57cec5SDimitry Andric OS << format("%.1f", floor((AverageTime2 * 10) + 0.5) / 10); 1660b57cec5SDimitry Andric OS.PadToColumn(27); 1678bcb0991SDimitry Andric if (!PrintingTotals) 1688bcb0991SDimitry Andric tryChangeColor(OS, Entry.CyclesSpentAfterWBAndBeforeRetire, 169e8d8bef9SDimitry Andric CumulativeExecutions, 170e8d8bef9SDimitry Andric getSubTargetInfo().getSchedModel().MicroOpBufferSize); 1710b57cec5SDimitry Andric OS << format("%.1f", floor((AverageTime3 * 10) + 0.5) / 10); 1720b57cec5SDimitry Andric 1730b57cec5SDimitry Andric if (OS.has_colors()) 1740b57cec5SDimitry Andric OS.resetColor(); 1750b57cec5SDimitry Andric OS.PadToColumn(34); 1760b57cec5SDimitry Andric } 1770b57cec5SDimitry Andric 1780b57cec5SDimitry Andric void TimelineView::printAverageWaitTimes(raw_ostream &OS) const { 1790b57cec5SDimitry Andric std::string Header = 1800b57cec5SDimitry Andric "\n\nAverage Wait times (based on the timeline view):\n" 1810b57cec5SDimitry Andric "[0]: Executions\n" 1820b57cec5SDimitry Andric "[1]: Average time spent waiting in a scheduler's queue\n" 1830b57cec5SDimitry Andric "[2]: Average time spent waiting in a scheduler's queue while ready\n" 1840b57cec5SDimitry Andric "[3]: Average time elapsed from WB until retire stage\n\n" 1850b57cec5SDimitry Andric " [0] [1] [2] [3]\n"; 1860b57cec5SDimitry Andric OS << Header; 1870b57cec5SDimitry Andric formatted_raw_ostream FOS(OS); 188e8d8bef9SDimitry Andric unsigned Executions = Timeline.size() / getSource().size(); 1890b57cec5SDimitry Andric unsigned IID = 0; 190e8d8bef9SDimitry Andric for (const MCInst &Inst : getSource()) { 1910b57cec5SDimitry Andric printWaitTimeEntry(FOS, WaitTime[IID], IID, Executions); 192e8d8bef9SDimitry Andric FOS << " " << printInstructionString(Inst) << '\n'; 1930b57cec5SDimitry Andric FOS.flush(); 1940b57cec5SDimitry Andric ++IID; 1950b57cec5SDimitry Andric } 1968bcb0991SDimitry Andric 1978bcb0991SDimitry Andric // If the timeline contains more than one instruction, 1988bcb0991SDimitry Andric // let's also print global averages. 199e8d8bef9SDimitry Andric if (getSource().size() != 1) { 2008bcb0991SDimitry Andric WaitTimeEntry TotalWaitTime = std::accumulate( 2018bcb0991SDimitry Andric WaitTime.begin(), WaitTime.end(), WaitTimeEntry{0, 0, 0}, 2028bcb0991SDimitry Andric [](const WaitTimeEntry &A, const WaitTimeEntry &B) { 2038bcb0991SDimitry Andric return WaitTimeEntry{ 2048bcb0991SDimitry Andric A.CyclesSpentInSchedulerQueue + B.CyclesSpentInSchedulerQueue, 2058bcb0991SDimitry Andric A.CyclesSpentInSQWhileReady + B.CyclesSpentInSQWhileReady, 2068bcb0991SDimitry Andric A.CyclesSpentAfterWBAndBeforeRetire + 2078bcb0991SDimitry Andric B.CyclesSpentAfterWBAndBeforeRetire}; 2088bcb0991SDimitry Andric }); 2098bcb0991SDimitry Andric printWaitTimeEntry(FOS, TotalWaitTime, IID, Executions); 2108bcb0991SDimitry Andric FOS << " " 2118bcb0991SDimitry Andric << "<total>" << '\n'; 212e8d8bef9SDimitry Andric FOS.flush(); 2138bcb0991SDimitry Andric } 2140b57cec5SDimitry Andric } 2150b57cec5SDimitry Andric 2160b57cec5SDimitry Andric void TimelineView::printTimelineViewEntry(formatted_raw_ostream &OS, 2170b57cec5SDimitry Andric const TimelineViewEntry &Entry, 2180b57cec5SDimitry Andric unsigned Iteration, 2190b57cec5SDimitry Andric unsigned SourceIndex) const { 2200b57cec5SDimitry Andric if (Iteration == 0 && SourceIndex == 0) 2210b57cec5SDimitry Andric OS << '\n'; 2220b57cec5SDimitry Andric OS << '[' << Iteration << ',' << SourceIndex << ']'; 2230b57cec5SDimitry Andric OS.PadToColumn(10); 2240b57cec5SDimitry Andric assert(Entry.CycleDispatched >= 0 && "Invalid TimelineViewEntry!"); 2250b57cec5SDimitry Andric unsigned CycleDispatched = static_cast<unsigned>(Entry.CycleDispatched); 2260b57cec5SDimitry Andric for (unsigned I = 0, E = CycleDispatched; I < E; ++I) 2270b57cec5SDimitry Andric OS << ((I % 5 == 0) ? '.' : ' '); 2280b57cec5SDimitry Andric OS << TimelineView::DisplayChar::Dispatched; 2290b57cec5SDimitry Andric if (CycleDispatched != Entry.CycleExecuted) { 2300b57cec5SDimitry Andric // Zero latency instructions have the same value for CycleDispatched, 2310b57cec5SDimitry Andric // CycleIssued and CycleExecuted. 2320b57cec5SDimitry Andric for (unsigned I = CycleDispatched + 1, E = Entry.CycleIssued; I < E; ++I) 2330b57cec5SDimitry Andric OS << TimelineView::DisplayChar::Waiting; 2340b57cec5SDimitry Andric if (Entry.CycleIssued == Entry.CycleExecuted) 2350b57cec5SDimitry Andric OS << TimelineView::DisplayChar::DisplayChar::Executed; 2360b57cec5SDimitry Andric else { 2370b57cec5SDimitry Andric if (CycleDispatched != Entry.CycleIssued) 2380b57cec5SDimitry Andric OS << TimelineView::DisplayChar::Executing; 2390b57cec5SDimitry Andric for (unsigned I = Entry.CycleIssued + 1, E = Entry.CycleExecuted; I < E; 2400b57cec5SDimitry Andric ++I) 2410b57cec5SDimitry Andric OS << TimelineView::DisplayChar::Executing; 2420b57cec5SDimitry Andric OS << TimelineView::DisplayChar::Executed; 2430b57cec5SDimitry Andric } 2440b57cec5SDimitry Andric } 2450b57cec5SDimitry Andric 2460b57cec5SDimitry Andric for (unsigned I = Entry.CycleExecuted + 1, E = Entry.CycleRetired; I < E; ++I) 2470b57cec5SDimitry Andric OS << TimelineView::DisplayChar::RetireLag; 248*fe6060f1SDimitry Andric if (Entry.CycleExecuted < Entry.CycleRetired) 2490b57cec5SDimitry Andric OS << TimelineView::DisplayChar::Retired; 2500b57cec5SDimitry Andric 2510b57cec5SDimitry Andric // Skip other columns. 2520b57cec5SDimitry Andric for (unsigned I = Entry.CycleRetired + 1, E = LastCycle; I <= E; ++I) 2530b57cec5SDimitry Andric OS << ((I % 5 == 0 || I == LastCycle) ? '.' : ' '); 2540b57cec5SDimitry Andric } 2550b57cec5SDimitry Andric 2560b57cec5SDimitry Andric static void printTimelineHeader(formatted_raw_ostream &OS, unsigned Cycles) { 2570b57cec5SDimitry Andric OS << "\n\nTimeline view:\n"; 2580b57cec5SDimitry Andric if (Cycles >= 10) { 2590b57cec5SDimitry Andric OS.PadToColumn(10); 2600b57cec5SDimitry Andric for (unsigned I = 0; I <= Cycles; ++I) { 2610b57cec5SDimitry Andric if (((I / 10) & 1) == 0) 2620b57cec5SDimitry Andric OS << ' '; 2630b57cec5SDimitry Andric else 2640b57cec5SDimitry Andric OS << I % 10; 2650b57cec5SDimitry Andric } 2660b57cec5SDimitry Andric OS << '\n'; 2670b57cec5SDimitry Andric } 2680b57cec5SDimitry Andric 2690b57cec5SDimitry Andric OS << "Index"; 2700b57cec5SDimitry Andric OS.PadToColumn(10); 2710b57cec5SDimitry Andric for (unsigned I = 0; I <= Cycles; ++I) { 2720b57cec5SDimitry Andric if (((I / 10) & 1) == 0) 2730b57cec5SDimitry Andric OS << I % 10; 2740b57cec5SDimitry Andric else 2750b57cec5SDimitry Andric OS << ' '; 2760b57cec5SDimitry Andric } 2770b57cec5SDimitry Andric OS << '\n'; 2780b57cec5SDimitry Andric } 2790b57cec5SDimitry Andric 2800b57cec5SDimitry Andric void TimelineView::printTimeline(raw_ostream &OS) const { 2810b57cec5SDimitry Andric formatted_raw_ostream FOS(OS); 2820b57cec5SDimitry Andric printTimelineHeader(FOS, LastCycle); 2830b57cec5SDimitry Andric FOS.flush(); 2840b57cec5SDimitry Andric 2850b57cec5SDimitry Andric unsigned IID = 0; 286e8d8bef9SDimitry Andric ArrayRef<llvm::MCInst> Source = getSource(); 2870b57cec5SDimitry Andric const unsigned Iterations = Timeline.size() / Source.size(); 2880b57cec5SDimitry Andric for (unsigned Iteration = 0; Iteration < Iterations; ++Iteration) { 2890b57cec5SDimitry Andric for (const MCInst &Inst : Source) { 2900b57cec5SDimitry Andric const TimelineViewEntry &Entry = Timeline[IID]; 291*fe6060f1SDimitry Andric // When an instruction is retired after timeline-max-cycles, 292*fe6060f1SDimitry Andric // its CycleRetired is left at 0. However, it's possible for 293*fe6060f1SDimitry Andric // a 0 latency instruction to be retired during cycle 0 and we 294*fe6060f1SDimitry Andric // don't want to early exit in that case. The CycleExecuted 295*fe6060f1SDimitry Andric // attribute is set correctly whether or not it is greater 296*fe6060f1SDimitry Andric // than timeline-max-cycles so we can use that to ensure 297*fe6060f1SDimitry Andric // we don't early exit because of a 0 latency instruction. 298*fe6060f1SDimitry Andric if (Entry.CycleRetired == 0 && Entry.CycleExecuted != 0) 2990b57cec5SDimitry Andric return; 3000b57cec5SDimitry Andric 3010b57cec5SDimitry Andric unsigned SourceIndex = IID % Source.size(); 3020b57cec5SDimitry Andric printTimelineViewEntry(FOS, Entry, Iteration, SourceIndex); 303e8d8bef9SDimitry Andric FOS << " " << printInstructionString(Inst) << '\n'; 3040b57cec5SDimitry Andric FOS.flush(); 3050b57cec5SDimitry Andric 3060b57cec5SDimitry Andric ++IID; 3070b57cec5SDimitry Andric } 3080b57cec5SDimitry Andric } 3090b57cec5SDimitry Andric } 310e8d8bef9SDimitry Andric 311e8d8bef9SDimitry Andric json::Value TimelineView::toJSON() const { 312e8d8bef9SDimitry Andric json::Array TimelineInfo; 313e8d8bef9SDimitry Andric 314e8d8bef9SDimitry Andric for (const TimelineViewEntry &TLE : Timeline) { 315e8d8bef9SDimitry Andric TimelineInfo.push_back( 316e8d8bef9SDimitry Andric json::Object({{"CycleDispatched", TLE.CycleDispatched}, 317e8d8bef9SDimitry Andric {"CycleReady", TLE.CycleReady}, 318e8d8bef9SDimitry Andric {"CycleIssued", TLE.CycleIssued}, 319e8d8bef9SDimitry Andric {"CycleExecuted", TLE.CycleExecuted}, 320e8d8bef9SDimitry Andric {"CycleRetired", TLE.CycleRetired}})); 321e8d8bef9SDimitry Andric } 322e8d8bef9SDimitry Andric return json::Object({{"TimelineInfo", std::move(TimelineInfo)}}); 323e8d8bef9SDimitry Andric } 3240b57cec5SDimitry Andric } // namespace mca 3250b57cec5SDimitry Andric } // namespace llvm 326