Cycle), as well as hardware resource pressure. The analysis and reporting style
were inspired by the IACA tool from Intel.
+:program:`llvm-mca` allows the usage of special code comments to mark regions of
+the assembly code to be analyzed. A comment starting with substring
+``LLVM-MCA-BEGIN`` marks the beginning of a code region. A comment starting with
+substring ``LLVM-MCA-END`` marks the end of a code region. For example:
+
+.. code-block:: none
+
+ # LLVM-MCA-BEGIN My Code Region
+ ...
+ # LLVM-MCA-END
+
+Multiple regions can be specified provided that they do not overlap. A code
+region can have an optional description. If no user defined region is specified,
+then :program:`llvm-mca` assumes a default region which contains every
+instruction in the input file. Every region is analyzed in isolation, and the
+final performance report is the union of all the reports generated for every
+code region.
+
OPTIONS
-------
--- /dev/null
+# RUN: not llvm-mca -mtriple=x86_64-unknown-unknown -mcpu=btver2 %s 2>&1 | FileCheck %s
+
+# LLVM-MCA-BEGIN
+# LLVM-MCA-END
+
+# LLVM-MCA-BEGIN
+# LLVM-MCA-END
+
+# CHECK: error: no assembly instructions found.
+
--- /dev/null
+# RUN: llvm-mca -mtriple=x86_64-unknown-unknown -mcpu=btver2 -iterations=1 -resource-pressure=false < %s | FileCheck %s
+
+ add %edi, %esi
+# LLVM-MCA-END
+ add %esi, %eax
+
+# CHECK: [0] Code Region - Default
+
+# CHECK: Iterations: 1
+# CHECK-NEXT: Instructions: 1
+# CHECK-NEXT: Total Cycles: 4
+# CHECK-NEXT: Dispatch Width: 2
+# CHECK-NEXT: IPC: 0.25
+
+# CHECK: Instruction Info:
+# CHECK-NEXT: [1]: #uOps
+# CHECK-NEXT: [2]: Latency
+# CHECK-NEXT: [3]: RThroughput
+# CHECK-NEXT: [4]: MayLoad
+# CHECK-NEXT: [5]: MayStore
+# CHECK-NEXT: [6]: HasSideEffects
+
+# CHECK: [1] [2] [3] [4] [5] [6] Instructions:
+# CHECK-NEXT: 1 1 0.50 addl %edi, %esi
+
--- /dev/null
+# RUN: llvm-mca -mtriple=x86_64-unknown-unknown -mcpu=btver2 -iterations=1 -resource-pressure=false < %s | FileCheck %s
+
+ add %esi, %edi
+# LLVM-MCA-BEGIN foo
+ add %edi, %eax
+
+# CHECK: [0] Code Region - foo
+
+# CHECK: Iterations: 1
+# CHECK-NEXT: Instructions: 1
+# CHECK-NEXT: Total Cycles: 4
+# CHECK-NEXT: Dispatch Width: 2
+# CHECK-NEXT: IPC: 0.25
+
+# CHECK: Instruction Info:
+# CHECK-NEXT: [1]: #uOps
+# CHECK-NEXT: [2]: Latency
+# CHECK-NEXT: [3]: RThroughput
+# CHECK-NEXT: [4]: MayLoad
+# CHECK-NEXT: [5]: MayStore
+# CHECK-NEXT: [6]: HasSideEffects
+
+# CHECK: [1] [2] [3] [4] [5] [6] Instructions:
+# CHECK-NEXT: 1 1 0.50 addl %edi, %eax
+
--- /dev/null
+# RUN: llvm-mca -mtriple=x86_64-unknown-unknown -mcpu=btver2 -iterations=1 -resource-pressure=false < %s | FileCheck %s
+
+# LLVM-MCA-BEGIN Empty
+ # Empty sequence
+# LLVM-MCA-END
+
+# LLVM-MCA-BEGIN NotEmpty
+ add %edi, %eax
+# LLVM-MCA-END
+
+# CHECK: [0] Code Region - NotEmpty
+
+# CHECK: Iterations: 1
+# CHECK-NEXT: Instructions: 1
+# CHECK-NEXT: Total Cycles: 4
+# CHECK-NEXT: Dispatch Width: 2
+# CHECK-NEXT: IPC: 0.25
+
+# CHECK: Instruction Info:
+# CHECK-NEXT: [1]: #uOps
+# CHECK-NEXT: [2]: Latency
+# CHECK-NEXT: [3]: RThroughput
+# CHECK-NEXT: [4]: MayLoad
+# CHECK-NEXT: [5]: MayStore
+# CHECK-NEXT: [6]: HasSideEffects
+
+# CHECK: [1] [2] [3] [4] [5] [6] Instructions:
+# CHECK-NEXT: 1 1 0.50 addl %edi, %eax
+
--- /dev/null
+# RUN: llvm-mca -mtriple=x86_64-unknown-unknown -mcpu=btver2 -iterations=1 -resource-pressure=false < %s | FileCheck %s
+
+ add %ecx, %esi
+# LLVM-MCA-BEGIN First Region
+ add %edi, %esi
+# LLVM-MCA-END
+# LLVM-MCA-BEGIN Second Region
+ add %esi, %edx
+# LLVM-MCA-END
+# LLVM-MCA-BEGIN Third Region
+ add %edx, %eax
+# LLVM-MCA-END
+ add %esi, %eax
+
+# CHECK: [0] Code Region - First Region
+
+# CHECK: Iterations: 1
+# CHECK-NEXT: Instructions: 1
+# CHECK-NEXT: Total Cycles: 4
+# CHECK-NEXT: Dispatch Width: 2
+# CHECK-NEXT: IPC: 0.25
+
+# CHECK: Instruction Info:
+# CHECK-NEXT: [1]: #uOps
+# CHECK-NEXT: [2]: Latency
+# CHECK-NEXT: [3]: RThroughput
+# CHECK-NEXT: [4]: MayLoad
+# CHECK-NEXT: [5]: MayStore
+# CHECK-NEXT: [6]: HasSideEffects
+
+# CHECK: [1] [2] [3] [4] [5] [6] Instructions:
+# CHECK-NEXT: 1 1 0.50 addl %edi, %esi
+
+# CHECK: [1] Code Region - Second Region
+
+# CHECK: Iterations: 1
+# CHECK-NEXT: Instructions: 1
+# CHECK-NEXT: Total Cycles: 4
+# CHECK-NEXT: Dispatch Width: 2
+# CHECK-NEXT: IPC: 0.25
+
+# CHECK: Instruction Info:
+# CHECK-NEXT: [1]: #uOps
+# CHECK-NEXT: [2]: Latency
+# CHECK-NEXT: [3]: RThroughput
+# CHECK-NEXT: [4]: MayLoad
+# CHECK-NEXT: [5]: MayStore
+# CHECK-NEXT: [6]: HasSideEffects
+
+# CHECK: [1] [2] [3] [4] [5] [6] Instructions:
+# CHECK-NEXT: 1 1 0.50 addl %esi, %edx
+
+# CHECK: [2] Code Region - Third Region
+
+# CHECK: Iterations: 1
+# CHECK-NEXT: Instructions: 1
+# CHECK-NEXT: Total Cycles: 4
+# CHECK-NEXT: Dispatch Width: 2
+# CHECK-NEXT: IPC: 0.25
+
+# CHECK: Instruction Info:
+# CHECK-NEXT: [1]: #uOps
+# CHECK-NEXT: [2]: Latency
+# CHECK-NEXT: [3]: RThroughput
+# CHECK-NEXT: [4]: MayLoad
+# CHECK-NEXT: [5]: MayStore
+# CHECK-NEXT: [6]: HasSideEffects
+
+# CHECK: [1] [2] [3] [4] [5] [6] Instructions:
+# CHECK-NEXT: 1 1 0.50 addl %edx, %eax
+
--- /dev/null
+# RUN: not llvm-mca -mtriple=x86_64-unknown-unknown -mcpu=btver2 %s 2>&1 | FileCheck %s
+
+# LLVM-MCA-BEGIN foo
+
+# LLVM-MCA-BEGIN bar
+
+# LLVM-MCA-END
+
+# CHECK: llvm-mca-markers-6.s:5:2: warning: Ignoring invalid region start
+# CHECK-NEXT: # LLVM-MCA-BEGIN bar
+# CHECK-NEXT: ^
+# CHECK-NEXT: error: no assembly instructions found.
+
--- /dev/null
+# RUN: not llvm-mca -mtriple=x86_64-unknown-unknown -mcpu=btver2 %s 2>&1 | FileCheck %s
+
+# LLVM-MCA-BEGIN foo
+
+# LLVM-MCA-END
+
+# LLVM-MCA-END
+
+# CHECK: llvm-mca-markers-7.s:7:2: warning: Ignoring invalid region end
+# CHECK-NEXT: # LLVM-MCA-END
+# CHECK-NEXT: ^
+
Backend.cpp
BackendPrinter.cpp
BackendStatistics.cpp
+ CodeRegion.cpp
Dispatch.cpp
HWEventListener.cpp
InstrBuilder.cpp
--- /dev/null
+//===-------------------------- CodeRegion.cpp -----------------*- C++ -* -===//
+//
+// The LLVM Compiler Infrastructure
+//
+// This file is distributed under the University of Illinois Open Source
+// License. See LICENSE.TXT for details.
+//
+//===----------------------------------------------------------------------===//
+/// \file
+///
+/// This file implements methods from the CodeRegions interface.
+///
+//===----------------------------------------------------------------------===//
+
+#include "CodeRegion.h"
+
+using namespace llvm;
+
+namespace mca {
+
+bool CodeRegion::isLocInRange(SMLoc Loc) const {
+ if (RangeEnd.isValid() && Loc.getPointer() > RangeEnd.getPointer())
+ return false;
+ if (RangeStart.isValid() && Loc.getPointer() < RangeStart.getPointer())
+ return false;
+ return true;
+}
+
+void CodeRegions::beginRegion(StringRef Description, SMLoc Loc) {
+ assert(!Regions.empty() && "Missing Default region");
+ const CodeRegion &CurrentRegion = *Regions.back();
+ if (CurrentRegion.startLoc().isValid() && !CurrentRegion.endLoc().isValid()) {
+ SM.PrintMessage(Loc, SourceMgr::DK_Warning,
+ "Ignoring invalid region start");
+ return;
+ }
+
+ // Remove the default region if there are user defined regions.
+ if (!CurrentRegion.startLoc().isValid())
+ Regions.erase(Regions.begin());
+ addRegion(Description, Loc);
+}
+
+void CodeRegions::endRegion(SMLoc Loc) {
+ assert(!Regions.empty() && "Missing Default region");
+ CodeRegion &CurrentRegion = *Regions.back();
+ if (CurrentRegion.endLoc().isValid()) {
+ SM.PrintMessage(Loc, SourceMgr::DK_Warning, "Ignoring invalid region end");
+ return;
+ }
+
+ CurrentRegion.setEndLocation(Loc);
+}
+
+void CodeRegions::addInstruction(std::unique_ptr<const MCInst> Instruction) {
+ const SMLoc &Loc = Instruction->getLoc();
+ const auto It =
+ std::find_if(Regions.rbegin(), Regions.rend(),
+ [Loc](const std::unique_ptr<CodeRegion> &Region) {
+ return Region->isLocInRange(Loc);
+ });
+ if (It != Regions.rend())
+ (*It)->addInstruction(std::move(Instruction));
+}
+
+} // namespace mca
--- /dev/null
+//===-------------------------- CodeRegion.h -------------------*- C++ -* -===//
+//
+// The LLVM Compiler Infrastructure
+//
+// This file is distributed under the University of Illinois Open Source
+// License. See LICENSE.TXT for details.
+//
+//===----------------------------------------------------------------------===//
+/// \file
+///
+/// This file implements class CodeRegion and CodeRegions.
+///
+/// A CodeRegion describes a region of assembly code guarded by special LLVM-MCA
+/// comment directives.
+///
+/// # LLVM-MCA-BEGIN foo
+/// ... ## asm
+/// # LLVM-MCA-END
+///
+/// A comment starting with substring LLVM-MCA-BEGIN marks the beginning of a
+/// new region of code.
+/// A comment starting with substring LLVM-MCA-END marks the end of the
+/// last-seen region of code.
+///
+/// Code regions are not allowed to overlap. Each region can have a optional
+/// description; internally, regions are described by a range of source
+/// locations (SMLoc objects).
+///
+/// An instruction (a MCInst) is added to a region R only if its location is in
+/// range [R.RangeStart, R.RangeEnd].
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_TOOLS_LLVM_MCA_CODEREGION_H
+#define LLVM_TOOLS_LLVM_MCA_CODEREGION_H
+
+#include "llvm/ADT/StringRef.h"
+#include "llvm/MC/MCInst.h"
+#include "llvm/Support/SMLoc.h"
+#include "llvm/Support/SourceMgr.h"
+#include <vector>
+
+namespace mca {
+
+/// \brief A region of assembly code.
+///
+/// It identifies a sequence of machine instructions.
+class CodeRegion {
+ // An optional descriptor for this region.
+ llvm::StringRef Description;
+ // Instructions that form this region.
+ std::vector<std::unique_ptr<const llvm::MCInst>> Instructions;
+ // Source location range.
+ llvm::SMLoc RangeStart;
+ llvm::SMLoc RangeEnd;
+
+ CodeRegion(const CodeRegion &) = delete;
+ CodeRegion &operator=(const CodeRegion &) = delete;
+
+public:
+ CodeRegion(llvm::StringRef Desc, llvm::SMLoc Start)
+ : Description(Desc), RangeStart(Start), RangeEnd() {}
+
+ void addInstruction(std::unique_ptr<const llvm::MCInst> Instruction) {
+ Instructions.emplace_back(std::move(Instruction));
+ }
+
+ llvm::SMLoc startLoc() const { return RangeStart; }
+ llvm::SMLoc endLoc() const { return RangeEnd; }
+
+ void setEndLocation(llvm::SMLoc End) { RangeEnd = End; }
+ bool empty() const { return Instructions.empty(); }
+ bool isLocInRange(llvm::SMLoc Loc) const;
+
+ const std::vector<std::unique_ptr<const llvm::MCInst>> &
+ getInstructions() const {
+ return Instructions;
+ }
+
+ llvm::StringRef getDescription() const { return Description; }
+};
+
+class CodeRegions {
+ // A source manager. Used by the tool to generate meaningful warnings.
+ llvm::SourceMgr &SM;
+
+ std::vector<std::unique_ptr<CodeRegion>> Regions;
+
+ // Construct a new region of code guarded by LLVM-MCA comments.
+ void addRegion(llvm::StringRef Description, llvm::SMLoc Loc) {
+ Regions.emplace_back(llvm::make_unique<CodeRegion>(Description, Loc));
+ }
+
+ CodeRegions(const CodeRegions &) = delete;
+ CodeRegions &operator=(const CodeRegions &) = delete;
+
+public:
+ typedef std::vector<std::unique_ptr<CodeRegion>>::iterator iterator;
+ typedef std::vector<std::unique_ptr<CodeRegion>>::const_iterator
+ const_iterator;
+
+ iterator begin() { return Regions.begin(); }
+ iterator end() { return Regions.end(); }
+ const_iterator begin() const { return Regions.cbegin(); }
+ const_iterator end() const { return Regions.cend(); }
+
+ void beginRegion(llvm::StringRef Description, llvm::SMLoc Loc);
+ void endRegion(llvm::SMLoc Loc);
+ void addInstruction(std::unique_ptr<const llvm::MCInst> Instruction);
+
+ CodeRegions(llvm::SourceMgr &S) : SM(S) {
+ // Create a default region for the input code sequence.
+ addRegion("Default", llvm::SMLoc());
+ }
+
+ const std::vector<std::unique_ptr<const llvm::MCInst>> &
+ getInstructionSequence(unsigned Idx) const {
+ return Regions[Idx]->getInstructions();
+ }
+
+ bool empty() const {
+ return std::all_of(Regions.begin(), Regions.end(),
+ [](const std::unique_ptr<CodeRegion> &Region) {
+ return Region->empty();
+ });
+ }
+};
+
+} // namespace mca
+
+#endif
class SourceMgr {
using InstVec = std::vector<std::unique_ptr<const llvm::MCInst>>;
- InstVec Sequence;
+ const InstVec &Sequence;
unsigned Current;
unsigned Iterations;
static const unsigned DefaultIterations = 70;
public:
- SourceMgr(unsigned NumIterations)
- : Current(0),
+ SourceMgr(const InstVec &MCInstSequence, unsigned NumIterations)
+ : Sequence(MCInstSequence), Current(0),
Iterations(NumIterations ? NumIterations : DefaultIterations) {}
unsigned getCurrentIteration() const { return Current / Sequence.size(); }
unsigned getNumIterations() const { return Iterations; }
unsigned size() const { return Sequence.size(); }
const InstVec &getSequence() const { return Sequence; }
- InstVec &getSequence() { return Sequence; }
bool hasNext() { return Current < (Iterations * size()); }
void updateNext() { Current++; }
#include "BackendPrinter.h"
#include "BackendStatistics.h"
+#include "CodeRegion.h"
#include "InstructionInfoView.h"
#include "InstructionTables.h"
#include "RegisterFileStatistics.h"
return TheTarget;
}
+// A comment consumer that parses strings.
+// The only valid tokens are strings.
+class MCACommentConsumer : public AsmCommentConsumer {
+public:
+ mca::CodeRegions &Regions;
+
+ MCACommentConsumer(mca::CodeRegions &R) : Regions(R) {}
+ void HandleComment(SMLoc Loc, StringRef CommentText) override {
+ // Skip empty comments.
+ StringRef Comment(CommentText);
+ if (Comment.empty())
+ return;
+
+ // Skip spaces and tabs
+ unsigned Position = Comment.find_first_not_of(" \t");
+ if (Position >= Comment.size())
+ // we reached the end of the comment. Bail out.
+ return;
+
+ Comment = Comment.drop_front(Position);
+ if (Comment.consume_front("LLVM-MCA-END")) {
+ Regions.endRegion(Loc);
+ return;
+ }
+
+ // Now try to parse string LLVM-MCA-BEGIN
+ if (!Comment.consume_front("LLVM-MCA-BEGIN"))
+ return;
+
+ // Skip spaces and tabs
+ Position = Comment.find_first_not_of(" \t");
+ if (Position < Comment.size())
+ Comment.drop_front(Position);
+ // Use the rest of the string as a descriptor for this code snippet.
+ Regions.beginRegion(Comment, Loc);
+ }
+};
+
int AssembleInput(const char *ProgName, MCAsmParser &Parser,
const Target *TheTarget, MCSubtargetInfo &STI,
MCInstrInfo &MCII, MCTargetOptions &MCOptions) {
}
class MCStreamerWrapper final : public MCStreamer {
- using InstVec = std::vector<std::unique_ptr<const MCInst>>;
- InstVec &Insts;
+ mca::CodeRegions &Regions;
public:
- MCStreamerWrapper(MCContext &Context, InstVec &Vec)
- : MCStreamer(Context), Insts(Vec) {}
+ MCStreamerWrapper(MCContext &Context, mca::CodeRegions &R)
+ : MCStreamer(Context), Regions(R) {}
// We only want to intercept the emission of new instructions.
virtual void EmitInstruction(const MCInst &Inst, const MCSubtargetInfo &STI,
bool /* unused */) override {
- Insts.emplace_back(new MCInst(Inst));
+ Regions.addInstruction(llvm::make_unique<const MCInst>(Inst));
}
bool EmitSymbolAttribute(MCSymbol *Symbol, MCSymbolAttr Attribute) override {
void EmitCOFFSymbolType(int Type) override {}
void EndCOFFSymbolDef() override {}
- const InstVec &GetInstructionSequence() const { return Insts; }
+ const std::vector<std::unique_ptr<const MCInst>> &
+ GetInstructionSequence(unsigned Index) const {
+ return Regions.getInstructionSequence(Index);
+ }
};
} // end of anonymous namespace
std::unique_ptr<buffer_ostream> BOS;
- std::unique_ptr<mca::SourceMgr> S = llvm::make_unique<mca::SourceMgr>(
- PrintInstructionTables ? 1 : Iterations);
- MCStreamerWrapper Str(Ctx, S->getSequence());
+ mca::CodeRegions Regions(SrcMgr);
+ MCStreamerWrapper Str(Ctx, Regions);
std::unique_ptr<MCInstrInfo> MCII(TheTarget->createMCInstrInfo());
std::unique_ptr<MCSubtargetInfo> STI(
}
std::unique_ptr<MCAsmParser> P(createMCAsmParser(SrcMgr, Ctx, Str, *MAI));
+ MCAsmLexer &Lexer = P->getLexer();
+ MCACommentConsumer CC(Regions);
+ Lexer.setCommentConsumer(&CC);
+
if (AssembleInput(ProgName, *P, TheTarget, *STI, *MCII, MCOptions))
return 1;
- if (S->isEmpty()) {
+ if (Regions.empty()) {
errs() << "error: no assembly instructions found.\n";
return 1;
}
// Create an instruction builder.
mca::InstrBuilder IB(*STI, *MCII);
- if (PrintInstructionTables) {
- mca::InstructionTables IT(STI->getSchedModel(), IB, *S);
-
- if (PrintInstructionInfoView) {
- IT.addView(
- llvm::make_unique<mca::InstructionInfoView>(*STI, *MCII, *S, *IP));
+ // Number each region in the sequence.
+ unsigned RegionIdx = 0;
+ for (const std::unique_ptr<mca::CodeRegion> &Region : Regions) {
+ // Skip empty code regions.
+ if (Region->empty())
+ continue;
+
+ // Don't print the header of this region if it is the default region, and
+ // it doesn't have an end location.
+ if (Region->startLoc().isValid() || Region->endLoc().isValid()) {
+ TOF->os() << "\n[" << RegionIdx++ << "] Code Region";
+ StringRef Desc = Region->getDescription();
+ if (!Desc.empty())
+ TOF->os() << " - " << Desc;
+ TOF->os() << "\n\n";
}
- IT.addView(llvm::make_unique<mca::ResourcePressureView>(*STI, *IP, *S));
- IT.run();
- IT.printReport(TOF->os());
- TOF->keep();
- return 0;
- }
+ mca::SourceMgr S(Region->getInstructions(),
+ PrintInstructionTables ? 1 : Iterations);
+
+ if (PrintInstructionTables) {
+ mca::InstructionTables IT(STI->getSchedModel(), IB, S);
+
+ if (PrintInstructionInfoView) {
+ IT.addView(
+ llvm::make_unique<mca::InstructionInfoView>(*STI, *MCII, S, *IP));
+ }
- mca::Backend B(*STI, *MRI, IB, *S, Width, RegisterFileSize, LoadQueueSize,
- StoreQueueSize, AssumeNoAlias);
- mca::BackendPrinter Printer(B);
+ IT.addView(llvm::make_unique<mca::ResourcePressureView>(*STI, *IP, S));
+ IT.run();
+ IT.printReport(TOF->os());
+ continue;
+ }
+
+ mca::Backend B(*STI, *MRI, IB, S, Width, RegisterFileSize, LoadQueueSize,
+ StoreQueueSize, AssumeNoAlias);
+ mca::BackendPrinter Printer(B);
- Printer.addView(llvm::make_unique<mca::SummaryView>(*S, Width));
+ Printer.addView(llvm::make_unique<mca::SummaryView>(S, Width));
+ if (PrintInstructionInfoView)
+ Printer.addView(
+ llvm::make_unique<mca::InstructionInfoView>(*STI, *MCII, S, *IP));
- if (PrintInstructionInfoView)
- Printer.addView(
- llvm::make_unique<mca::InstructionInfoView>(*STI, *MCII, *S, *IP));
+ if (PrintModeVerbose)
+ Printer.addView(llvm::make_unique<mca::BackendStatistics>(*STI));
- if (PrintModeVerbose)
- Printer.addView(llvm::make_unique<mca::BackendStatistics>(*STI));
+ if (PrintRegisterFileStats)
+ Printer.addView(llvm::make_unique<mca::RegisterFileStatistics>(*STI));
- if (PrintRegisterFileStats)
- Printer.addView(llvm::make_unique<mca::RegisterFileStatistics>(*STI));
+ if (PrintResourcePressureView)
+ Printer.addView(
+ llvm::make_unique<mca::ResourcePressureView>(*STI, *IP, S));
- if (PrintResourcePressureView)
- Printer.addView(
- llvm::make_unique<mca::ResourcePressureView>(*STI, *IP, *S));
+ if (PrintTimelineView) {
+ Printer.addView(llvm::make_unique<mca::TimelineView>(
+ *STI, *IP, S, TimelineMaxIterations, TimelineMaxCycles));
+ }
- if (PrintTimelineView) {
- Printer.addView(llvm::make_unique<mca::TimelineView>(
- *STI, *IP, *S, TimelineMaxIterations, TimelineMaxCycles));
+ B.run();
+ Printer.printReport(TOF->os());
}
- B.run();
- Printer.printReport(TOF->os());
TOF->keep();
-
return 0;
}