blob: b4891f1f1dbd305de0258e50a1fbce69c63779bd [file] [log] [blame]
Clement Courbetac74acd2018-04-04 11:37:06 +00001//===-- llvm-exegesis.cpp ---------------------------------------*- C++ -*-===//
2//
3// The LLVM Compiler Infrastructure
4//
5// This file is distributed under the University of Illinois Open Source
6// License. See LICENSE.TXT for details.
7//
8//===----------------------------------------------------------------------===//
9///
10/// \file
11/// Measures execution properties (latencies/uops) of an instruction.
12///
13//===----------------------------------------------------------------------===//
14
Clement Courbet37f0ca02018-05-15 12:08:00 +000015#include "lib/Analysis.h"
Clement Courbetac74acd2018-04-04 11:37:06 +000016#include "lib/BenchmarkResult.h"
17#include "lib/BenchmarkRunner.h"
Clement Courbet37f0ca02018-05-15 12:08:00 +000018#include "lib/Clustering.h"
Clement Courbetac74acd2018-04-04 11:37:06 +000019#include "lib/LlvmState.h"
20#include "lib/PerfHelper.h"
Clement Courbet4860b982018-06-26 08:49:30 +000021#include "lib/Target.h"
Clement Courbetac74acd2018-04-04 11:37:06 +000022#include "llvm/ADT/StringExtras.h"
23#include "llvm/ADT/Twine.h"
24#include "llvm/MC/MCInstBuilder.h"
Clement Courbet78b2e732018-09-25 07:31:44 +000025#include "llvm/MC/MCObjectFileInfo.h"
26#include "llvm/MC/MCParser/MCAsmParser.h"
27#include "llvm/MC/MCParser/MCTargetAsmParser.h"
Clement Courbetac74acd2018-04-04 11:37:06 +000028#include "llvm/MC/MCRegisterInfo.h"
Clement Courbet78b2e732018-09-25 07:31:44 +000029#include "llvm/MC/MCStreamer.h"
Clement Courbet37f0ca02018-05-15 12:08:00 +000030#include "llvm/MC/MCSubtargetInfo.h"
Clement Courbet78b2e732018-09-25 07:31:44 +000031#include "llvm/Object/ObjectFile.h"
Clement Courbetac74acd2018-04-04 11:37:06 +000032#include "llvm/Support/CommandLine.h"
Clement Courbet37f0ca02018-05-15 12:08:00 +000033#include "llvm/Support/Format.h"
Clement Courbetac74acd2018-04-04 11:37:06 +000034#include "llvm/Support/Path.h"
Clement Courbet78b2e732018-09-25 07:31:44 +000035#include "llvm/Support/SourceMgr.h"
Clement Courbet37f0ca02018-05-15 12:08:00 +000036#include "llvm/Support/TargetRegistry.h"
Clement Courbetac74acd2018-04-04 11:37:06 +000037#include "llvm/Support/TargetSelect.h"
38#include <algorithm>
Clement Courbetac74acd2018-04-04 11:37:06 +000039#include <string>
Clement Courbetac74acd2018-04-04 11:37:06 +000040
41static llvm::cl::opt<unsigned>
42 OpcodeIndex("opcode-index", llvm::cl::desc("opcode to measure, by index"),
43 llvm::cl::init(0));
44
45static llvm::cl::opt<std::string>
46 OpcodeName("opcode-name", llvm::cl::desc("opcode to measure, by name"),
47 llvm::cl::init(""));
48
Clement Courbet37f0ca02018-05-15 12:08:00 +000049static llvm::cl::opt<std::string>
Clement Courbet78b2e732018-09-25 07:31:44 +000050 SnippetsFile("snippets-file", llvm::cl::desc("code snippets to measure"),
51 llvm::cl::init(""));
52
53static llvm::cl::opt<std::string>
Guillaume Chatelet8c91d4c2018-06-07 07:51:16 +000054 BenchmarkFile("benchmarks-file", llvm::cl::desc(""), llvm::cl::init(""));
Clement Courbet37f0ca02018-05-15 12:08:00 +000055
Clement Courbet4860b982018-06-26 08:49:30 +000056static llvm::cl::opt<exegesis::InstructionBenchmark::ModeE> BenchmarkMode(
Clement Courbet5ec03cd2018-05-18 12:33:57 +000057 "mode", llvm::cl::desc("the mode to run"),
Clement Courbet4860b982018-06-26 08:49:30 +000058 llvm::cl::values(clEnumValN(exegesis::InstructionBenchmark::Latency,
59 "latency", "Instruction Latency"),
60 clEnumValN(exegesis::InstructionBenchmark::Uops, "uops",
61 "Uop Decomposition"),
62 // When not asking for a specific benchmark mode, we'll
63 // analyse the results.
64 clEnumValN(exegesis::InstructionBenchmark::Unknown,
65 "analysis", "Analysis")));
Clement Courbetac74acd2018-04-04 11:37:06 +000066
67static llvm::cl::opt<unsigned>
68 NumRepetitions("num-repetitions",
69 llvm::cl::desc("number of time to repeat the asm snippet"),
70 llvm::cl::init(10000));
71
Clement Courbete752fd62018-06-18 11:27:47 +000072static llvm::cl::opt<bool> IgnoreInvalidSchedClass(
73 "ignore-invalid-sched-class",
74 llvm::cl::desc("ignore instructions that do not define a sched class"),
75 llvm::cl::init(false));
76
Clement Courbet37f0ca02018-05-15 12:08:00 +000077static llvm::cl::opt<unsigned> AnalysisNumPoints(
78 "analysis-numpoints",
79 llvm::cl::desc("minimum number of points in an analysis cluster"),
80 llvm::cl::init(3));
81
82static llvm::cl::opt<float>
83 AnalysisEpsilon("analysis-epsilon",
84 llvm::cl::desc("dbscan epsilon for analysis clustering"),
85 llvm::cl::init(0.1));
86
Clement Courbetcf210742018-05-17 13:41:28 +000087static llvm::cl::opt<std::string>
88 AnalysisClustersOutputFile("analysis-clusters-output-file",
89 llvm::cl::desc(""), llvm::cl::init("-"));
90static llvm::cl::opt<std::string>
91 AnalysisInconsistenciesOutputFile("analysis-inconsistencies-output-file",
92 llvm::cl::desc(""), llvm::cl::init("-"));
Clement Courbetcaa163e2018-05-16 09:50:04 +000093
Clement Courbetac74acd2018-04-04 11:37:06 +000094namespace exegesis {
95
Guillaume Chatelet8c91d4c2018-06-07 07:51:16 +000096static llvm::ExitOnError ExitOnErr;
97
Clement Courbet44b4c542018-06-19 11:28:59 +000098#ifdef LLVM_EXEGESIS_INITIALIZE_NATIVE_TARGET
99void LLVM_EXEGESIS_INITIALIZE_NATIVE_TARGET();
100#endif
101
Clement Courbet78b2e732018-09-25 07:31:44 +0000102// Checks that only one of OpcodeName, OpcodeIndex or SnippetsFile is provided,
103// and returns the opcode index or 0 if snippets should be read from
104// `SnippetsFile`.
105static unsigned getOpcodeOrDie(const llvm::MCInstrInfo &MCInstrInfo) {
106 const size_t NumSetFlags = (OpcodeName.empty() ? 0 : 1) +
107 (OpcodeIndex == 0 ? 0 : 1) +
108 (SnippetsFile.empty() ? 0 : 1);
109 if (NumSetFlags != 1)
Clement Courbet0e69e2d2018-05-17 10:52:18 +0000110 llvm::report_fatal_error(
Clement Courbet78b2e732018-09-25 07:31:44 +0000111 "please provide one and only one of 'opcode-index', 'opcode-name' or "
112 "'snippets-file'");
113 if (!SnippetsFile.empty())
114 return 0;
Clement Courbet0e69e2d2018-05-17 10:52:18 +0000115 if (OpcodeIndex > 0)
116 return OpcodeIndex;
117 // Resolve opcode name -> opcode.
118 for (unsigned I = 0, E = MCInstrInfo.getNumOpcodes(); I < E; ++I)
119 if (MCInstrInfo.getName(I) == OpcodeName)
120 return I;
121 llvm::report_fatal_error(llvm::Twine("unknown opcode ").concat(OpcodeName));
122}
123
Clement Courbetd939f6d2018-09-13 07:40:53 +0000124// Generates code snippets for opcode `Opcode`.
Clement Courbet78b2e732018-09-25 07:31:44 +0000125static llvm::Expected<std::vector<BenchmarkCode>>
Clement Courbet79587352018-09-13 08:06:29 +0000126generateSnippets(const LLVMState &State, unsigned Opcode) {
Guillaume Chatelet9b592382018-10-10 14:57:32 +0000127 const Instruction Instr(State, Opcode);
128 const llvm::MCInstrDesc &InstrDesc = *Instr.Description;
Clement Courbetd939f6d2018-09-13 07:40:53 +0000129 // Ignore instructions that we cannot run.
130 if (InstrDesc.isPseudo())
131 return llvm::make_error<BenchmarkFailure>("Unsupported opcode: isPseudo");
132 if (InstrDesc.isBranch() || InstrDesc.isIndirectBranch())
133 return llvm::make_error<BenchmarkFailure>(
134 "Unsupported opcode: isBranch/isIndirectBranch");
135 if (InstrDesc.isCall() || InstrDesc.isReturn())
136 return llvm::make_error<BenchmarkFailure>(
137 "Unsupported opcode: isCall/isReturn");
138
Guillaume Chatelet9b592382018-10-10 14:57:32 +0000139 const std::unique_ptr<SnippetGenerator> Generator =
140 State.getExegesisTarget().createSnippetGenerator(BenchmarkMode, State);
141 if (!Generator)
142 llvm::report_fatal_error("cannot create snippet generator");
143 return Generator->generateConfigurations(Instr);
Clement Courbetd939f6d2018-09-13 07:40:53 +0000144}
145
Clement Courbet78b2e732018-09-25 07:31:44 +0000146namespace {
147
148// An MCStreamer that reads a BenchmarkCode definition from a file.
149// The BenchmarkCode definition is just an asm file, with additional comments to
150// specify which registers should be defined or are live on entry.
151class BenchmarkCodeStreamer : public llvm::MCStreamer,
152 public llvm::AsmCommentConsumer {
153public:
154 explicit BenchmarkCodeStreamer(llvm::MCContext *Context,
155 const llvm::MCRegisterInfo *TheRegInfo,
156 BenchmarkCode *Result)
157 : llvm::MCStreamer(*Context), RegInfo(TheRegInfo), Result(Result) {}
158
159 // Implementation of the llvm::MCStreamer interface. We only care about
160 // instructions.
Fangrui Song22438a82018-09-27 06:10:15 +0000161 void EmitInstruction(const llvm::MCInst &Instruction,
162 const llvm::MCSubtargetInfo &STI,
Clement Courbet78b2e732018-09-25 07:31:44 +0000163 bool PrintSchedInfo) override {
Fangrui Song22438a82018-09-27 06:10:15 +0000164 Result->Instructions.push_back(Instruction);
Clement Courbet78b2e732018-09-25 07:31:44 +0000165 }
166
167 // Implementation of the llvm::AsmCommentConsumer.
168 void HandleComment(llvm::SMLoc Loc, llvm::StringRef CommentText) override {
169 CommentText = CommentText.trim();
170 if (!CommentText.consume_front("LLVM-EXEGESIS-"))
171 return;
172 if (CommentText.consume_front("DEFREG")) {
173 // LLVM-EXEGESIS-DEFREF <reg> <hex_value>
174 RegisterValue RegVal;
175 llvm::SmallVector<llvm::StringRef, 2> Parts;
176 CommentText.split(Parts, ' ', /*unlimited splits*/ -1,
177 /*do not keep empty strings*/ false);
178 if (Parts.size() != 2) {
179 llvm::errs() << "invalid comment 'LLVM-EXEGESIS-DEFREG " << CommentText
180 << "\n";
181 ++InvalidComments;
182 }
183 if (!(RegVal.Register = findRegisterByName(Parts[0].trim()))) {
184 llvm::errs() << "unknown register in 'LLVM-EXEGESIS-DEFREG "
185 << CommentText << "\n";
186 ++InvalidComments;
187 return;
188 }
189 const llvm::StringRef HexValue = Parts[1].trim();
190 RegVal.Value = llvm::APInt(
191 /* each hex digit is 4 bits */ HexValue.size() * 4, HexValue, 16);
192 Result->RegisterInitialValues.push_back(std::move(RegVal));
193 return;
194 }
195 if (CommentText.consume_front("LIVEIN")) {
196 // LLVM-EXEGESIS-LIVEIN <reg>
197 if (unsigned Reg = findRegisterByName(CommentText.ltrim()))
198 Result->LiveIns.push_back(Reg);
199 else {
200 llvm::errs() << "unknown register in 'LLVM-EXEGESIS-LIVEIN "
201 << CommentText << "\n";
202 ++InvalidComments;
203 }
204 return;
205 }
206 }
207
208 unsigned numInvalidComments() const { return InvalidComments; }
209
210private:
211 // We only care about instructions, we don't implement this part of the API.
Fangrui Song22438a82018-09-27 06:10:15 +0000212 void EmitCommonSymbol(llvm::MCSymbol *Symbol, uint64_t Size,
213 unsigned ByteAlignment) override {}
214 bool EmitSymbolAttribute(llvm::MCSymbol *Symbol,
215 llvm::MCSymbolAttr Attribute) override {
Clement Courbet78b2e732018-09-25 07:31:44 +0000216 return false;
217 }
Fangrui Song22438a82018-09-27 06:10:15 +0000218 void EmitValueToAlignment(unsigned ByteAlignment, int64_t Value,
219 unsigned ValueSize,
220 unsigned MaxBytesToEmit) override {}
221 void EmitZerofill(llvm::MCSection *Section, llvm::MCSymbol *Symbol,
222 uint64_t Size, unsigned ByteAlignment,
Clement Courbet78b2e732018-09-25 07:31:44 +0000223 llvm::SMLoc Loc) override {}
224
225 unsigned findRegisterByName(const llvm::StringRef RegName) const {
226 // FIXME: Can we do better than this ?
227 for (unsigned I = 0, E = RegInfo->getNumRegs(); I < E; ++I) {
228 if (RegName == RegInfo->getName(I))
229 return I;
230 }
231 llvm::errs() << "'" << RegName
232 << "' is not a valid register name for the target\n";
233 return 0;
234 }
235
236 const llvm::MCRegisterInfo *const RegInfo;
237 BenchmarkCode *const Result;
238 unsigned InvalidComments = 0;
239};
240
241} // namespace
242
243// Reads code snippets from file `Filename`.
244static llvm::Expected<std::vector<BenchmarkCode>>
245readSnippets(const LLVMState &State, llvm::StringRef Filename) {
246 llvm::ErrorOr<std::unique_ptr<llvm::MemoryBuffer>> BufferPtr =
247 llvm::MemoryBuffer::getFileOrSTDIN(Filename);
248 if (std::error_code EC = BufferPtr.getError()) {
249 return llvm::make_error<BenchmarkFailure>(
250 "cannot read snippet: " + Filename + ": " + EC.message());
251 }
252 llvm::SourceMgr SM;
253 SM.AddNewSourceBuffer(std::move(BufferPtr.get()), llvm::SMLoc());
254
255 BenchmarkCode Result;
256
257 llvm::MCObjectFileInfo ObjectFileInfo;
258 const llvm::TargetMachine &TM = State.getTargetMachine();
259 llvm::MCContext Context(TM.getMCAsmInfo(), TM.getMCRegisterInfo(),
260 &ObjectFileInfo);
261 ObjectFileInfo.InitMCObjectFileInfo(TM.getTargetTriple(), /*PIC*/ false,
262 Context);
263 BenchmarkCodeStreamer Streamer(&Context, TM.getMCRegisterInfo(), &Result);
264 const std::unique_ptr<llvm::MCAsmParser> AsmParser(
265 llvm::createMCAsmParser(SM, Context, Streamer, *TM.getMCAsmInfo()));
266 if (!AsmParser)
267 return llvm::make_error<BenchmarkFailure>("cannot create asm parser");
268 AsmParser->getLexer().setCommentConsumer(&Streamer);
269
270 const std::unique_ptr<llvm::MCTargetAsmParser> TargetAsmParser(
271 TM.getTarget().createMCAsmParser(*TM.getMCSubtargetInfo(), *AsmParser,
272 *TM.getMCInstrInfo(),
273 llvm::MCTargetOptions()));
274
275 if (!TargetAsmParser)
276 return llvm::make_error<BenchmarkFailure>(
277 "cannot create target asm parser");
278 AsmParser->setTargetParser(*TargetAsmParser);
279
280 if (AsmParser->Run(false))
281 return llvm::make_error<BenchmarkFailure>("cannot parse asm file");
282 if (Streamer.numInvalidComments())
283 return llvm::make_error<BenchmarkFailure>(
284 llvm::Twine("found ")
285 .concat(llvm::Twine(Streamer.numInvalidComments()))
286 .concat(" invalid LLVM-EXEGESIS comments"));
287 return std::vector<BenchmarkCode>{std::move(Result)};
288}
289
Clement Courbet37f0ca02018-05-15 12:08:00 +0000290void benchmarkMain() {
291 if (exegesis::pfm::pfmInitialize())
292 llvm::report_fatal_error("cannot initialize libpfm");
293
Clement Courbetac74acd2018-04-04 11:37:06 +0000294 llvm::InitializeNativeTarget();
295 llvm::InitializeNativeTargetAsmPrinter();
Clement Courbet78b2e732018-09-25 07:31:44 +0000296 llvm::InitializeNativeTargetAsmParser();
Clement Courbet44b4c542018-06-19 11:28:59 +0000297#ifdef LLVM_EXEGESIS_INITIALIZE_NATIVE_TARGET
298 LLVM_EXEGESIS_INITIALIZE_NATIVE_TARGET();
299#endif
Clement Courbetac74acd2018-04-04 11:37:06 +0000300
Clement Courbetac74acd2018-04-04 11:37:06 +0000301 const LLVMState State;
Clement Courbet78b2e732018-09-25 07:31:44 +0000302 const auto Opcode = getOpcodeOrDie(State.getInstrInfo());
Clement Courbete752fd62018-06-18 11:27:47 +0000303
Clement Courbet78b2e732018-09-25 07:31:44 +0000304 std::vector<BenchmarkCode> Configurations;
305 if (Opcode > 0) {
306 // Ignore instructions without a sched class if -ignore-invalid-sched-class
307 // is passed.
308 if (IgnoreInvalidSchedClass &&
309 State.getInstrInfo().get(Opcode).getSchedClass() == 0) {
310 llvm::errs() << "ignoring instruction without sched class\n";
311 return;
312 }
313 Configurations = ExitOnErr(generateSnippets(State, Opcode));
314 } else {
315 Configurations = ExitOnErr(readSnippets(State, SnippetsFile));
Clement Courbete752fd62018-06-18 11:27:47 +0000316 }
Clement Courbetac74acd2018-04-04 11:37:06 +0000317
Clement Courbet4860b982018-06-26 08:49:30 +0000318 const std::unique_ptr<BenchmarkRunner> Runner =
319 State.getExegesisTarget().createBenchmarkRunner(BenchmarkMode, State);
320 if (!Runner) {
321 llvm::report_fatal_error("cannot create benchmark runner");
Clement Courbetac74acd2018-04-04 11:37:06 +0000322 }
323
Clement Courbet0e69e2d2018-05-17 10:52:18 +0000324 if (NumRepetitions == 0)
325 llvm::report_fatal_error("--num-repetitions must be greater than zero");
326
Guillaume Chatelet8c91d4c2018-06-07 07:51:16 +0000327 // Write to standard output if file is not set.
328 if (BenchmarkFile.empty())
329 BenchmarkFile = "-";
330
Clement Courbetd939f6d2018-09-13 07:40:53 +0000331 for (const BenchmarkCode &Conf : Configurations) {
332 InstructionBenchmark Result =
333 Runner->runConfiguration(Conf, NumRepetitions);
Guillaume Chatelet55ad0872018-09-25 12:18:08 +0000334 ExitOnErr(Result.writeYaml(State, BenchmarkFile));
Clement Courbetd939f6d2018-09-13 07:40:53 +0000335 }
Clement Courbet37f0ca02018-05-15 12:08:00 +0000336 exegesis::pfm::pfmTerminate();
337}
338
Clement Courbetcf210742018-05-17 13:41:28 +0000339// Prints the results of running analysis pass `Pass` to file `OutputFilename`
340// if OutputFilename is non-empty.
341template <typename Pass>
342static void maybeRunAnalysis(const Analysis &Analyzer, const std::string &Name,
Clement Courbet53d35d22018-06-05 10:56:19 +0000343 const std::string &OutputFilename) {
Clement Courbetcf210742018-05-17 13:41:28 +0000344 if (OutputFilename.empty())
345 return;
346 if (OutputFilename != "-") {
347 llvm::errs() << "Printing " << Name << " results to file '"
348 << OutputFilename << "'\n";
349 }
350 std::error_code ErrorCode;
351 llvm::raw_fd_ostream ClustersOS(OutputFilename, ErrorCode,
Zachary Turner1f67a3c2018-06-07 19:58:58 +0000352 llvm::sys::fs::FA_Read |
353 llvm::sys::fs::FA_Write);
354 if (ErrorCode)
355 llvm::report_fatal_error("cannot open out file: " + OutputFilename);
356 if (auto Err = Analyzer.run<Pass>(ClustersOS))
357 llvm::report_fatal_error(std::move(Err));
Clement Courbetcf210742018-05-17 13:41:28 +0000358}
359
360static void analysisMain() {
Guillaume Chatelet8c91d4c2018-06-07 07:51:16 +0000361 if (BenchmarkFile.empty())
362 llvm::report_fatal_error("--benchmarks-file must be set.");
363
Clement Courbet53d35d22018-06-05 10:56:19 +0000364 llvm::InitializeNativeTarget();
365 llvm::InitializeNativeTargetAsmPrinter();
Clement Courbet4273e1e2018-06-15 07:30:45 +0000366 llvm::InitializeNativeTargetDisassembler();
Clement Courbet37f0ca02018-05-15 12:08:00 +0000367 // Read benchmarks.
Clement Courbet53d35d22018-06-05 10:56:19 +0000368 const LLVMState State;
Clement Courbet37f0ca02018-05-15 12:08:00 +0000369 const std::vector<InstructionBenchmark> Points =
Guillaume Chatelet55ad0872018-09-25 12:18:08 +0000370 ExitOnErr(InstructionBenchmark::readYamls(State, BenchmarkFile));
Clement Courbet37f0ca02018-05-15 12:08:00 +0000371 llvm::outs() << "Parsed " << Points.size() << " benchmark points\n";
372 if (Points.empty()) {
373 llvm::errs() << "no benchmarks to analyze\n";
374 return;
375 }
376 // FIXME: Check that all points have the same triple/cpu.
377 // FIXME: Merge points from several runs (latency and uops).
378
Clement Courbet37f0ca02018-05-15 12:08:00 +0000379 std::string Error;
380 const auto *TheTarget =
381 llvm::TargetRegistry::lookupTarget(Points[0].LLVMTriple, Error);
382 if (!TheTarget) {
383 llvm::errs() << "unknown target '" << Points[0].LLVMTriple << "'\n";
384 return;
385 }
Guillaume Chatelet64165922018-06-11 09:18:01 +0000386 const auto Clustering = ExitOnErr(InstructionBenchmarkClustering::create(
Clement Courbet37f0ca02018-05-15 12:08:00 +0000387 Points, AnalysisNumPoints, AnalysisEpsilon));
Clement Courbet6d6c1a92018-05-16 08:47:21 +0000388
389 const Analysis Analyzer(*TheTarget, Clustering);
390
Clement Courbetcf210742018-05-17 13:41:28 +0000391 maybeRunAnalysis<Analysis::PrintClusters>(Analyzer, "analysis clusters",
392 AnalysisClustersOutputFile);
393 maybeRunAnalysis<Analysis::PrintSchedClassInconsistencies>(
394 Analyzer, "sched class consistency analysis",
395 AnalysisInconsistenciesOutputFile);
Clement Courbetac74acd2018-04-04 11:37:06 +0000396}
397
398} // namespace exegesis
399
400int main(int Argc, char **Argv) {
401 llvm::cl::ParseCommandLineOptions(Argc, Argv, "");
402
Guillaume Chatelet64165922018-06-11 09:18:01 +0000403 exegesis::ExitOnErr.setExitCodeMapper([](const llvm::Error &Err) {
404 if (Err.isA<llvm::StringError>())
405 return EXIT_SUCCESS;
406 return EXIT_FAILURE;
407 });
408
Clement Courbet4860b982018-06-26 08:49:30 +0000409 if (BenchmarkMode == exegesis::InstructionBenchmark::Unknown) {
Clement Courbet37f0ca02018-05-15 12:08:00 +0000410 exegesis::analysisMain();
411 } else {
412 exegesis::benchmarkMain();
Clement Courbetac74acd2018-04-04 11:37:06 +0000413 }
Clement Courbetac74acd2018-04-04 11:37:06 +0000414 return EXIT_SUCCESS;
415}