1 //===-- llvm-exegesis.cpp ---------------------------------------*- C++ -*-===//
3 // The LLVM Compiler Infrastructure
5 // This file is distributed under the University of Illinois Open Source
6 // License. See LICENSE.TXT for details.
8 //===----------------------------------------------------------------------===//
11 /// Measures execution properties (latencies/uops) of an instruction.
13 //===----------------------------------------------------------------------===//
15 #include "lib/Analysis.h"
16 #include "lib/BenchmarkResult.h"
17 #include "lib/BenchmarkRunner.h"
18 #include "lib/Clustering.h"
19 #include "lib/LlvmState.h"
20 #include "lib/PerfHelper.h"
21 #include "lib/Target.h"
22 #include "llvm/ADT/StringExtras.h"
23 #include "llvm/ADT/Twine.h"
24 #include "llvm/MC/MCInstBuilder.h"
25 #include "llvm/MC/MCObjectFileInfo.h"
26 #include "llvm/MC/MCParser/MCAsmParser.h"
27 #include "llvm/MC/MCParser/MCTargetAsmParser.h"
28 #include "llvm/MC/MCRegisterInfo.h"
29 #include "llvm/MC/MCStreamer.h"
30 #include "llvm/MC/MCSubtargetInfo.h"
31 #include "llvm/Object/ObjectFile.h"
32 #include "llvm/Support/CommandLine.h"
33 #include "llvm/Support/Format.h"
34 #include "llvm/Support/Path.h"
35 #include "llvm/Support/SourceMgr.h"
36 #include "llvm/Support/TargetRegistry.h"
37 #include "llvm/Support/TargetSelect.h"
41 static llvm::cl::opt
<unsigned>
42 OpcodeIndex("opcode-index", llvm::cl::desc("opcode to measure, by index"),
45 static llvm::cl::opt
<std::string
>
46 OpcodeName("opcode-name", llvm::cl::desc("opcode to measure, by name"),
49 static llvm::cl::opt
<std::string
>
50 SnippetsFile("snippets-file", llvm::cl::desc("code snippets to measure"),
53 static llvm::cl::opt
<std::string
>
54 BenchmarkFile("benchmarks-file", llvm::cl::desc(""), llvm::cl::init(""));
56 static llvm::cl::opt
<exegesis::InstructionBenchmark::ModeE
> BenchmarkMode(
57 "mode", llvm::cl::desc("the mode to run"),
58 llvm::cl::values(clEnumValN(exegesis::InstructionBenchmark::Latency
,
59 "latency", "Instruction Latency"),
60 clEnumValN(exegesis::InstructionBenchmark::Uops
, "uops",
62 // When not asking for a specific benchmark mode, we'll
63 // analyse the results.
64 clEnumValN(exegesis::InstructionBenchmark::Unknown
,
65 "analysis", "Analysis")));
67 static 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));
72 static 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));
77 static llvm::cl::opt
<unsigned> AnalysisNumPoints(
79 llvm::cl::desc("minimum number of points in an analysis cluster"),
82 static llvm::cl::opt
<float>
83 AnalysisEpsilon("analysis-epsilon",
84 llvm::cl::desc("dbscan epsilon for analysis clustering"),
87 static llvm::cl::opt
<std::string
>
88 AnalysisClustersOutputFile("analysis-clusters-output-file",
89 llvm::cl::desc(""), llvm::cl::init("-"));
90 static llvm::cl::opt
<std::string
>
91 AnalysisInconsistenciesOutputFile("analysis-inconsistencies-output-file",
92 llvm::cl::desc(""), llvm::cl::init("-"));
96 static llvm::ExitOnError ExitOnErr
;
98 #ifdef LLVM_EXEGESIS_INITIALIZE_NATIVE_TARGET
99 void LLVM_EXEGESIS_INITIALIZE_NATIVE_TARGET();
102 // 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
105 static 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)
110 llvm::report_fatal_error(
111 "please provide one and only one of 'opcode-index', 'opcode-name' or "
113 if (!SnippetsFile
.empty())
117 // Resolve opcode name -> opcode.
118 for (unsigned I
= 0, E
= MCInstrInfo
.getNumOpcodes(); I
< E
; ++I
)
119 if (MCInstrInfo
.getName(I
) == OpcodeName
)
121 llvm::report_fatal_error(llvm::Twine("unknown opcode ").concat(OpcodeName
));
124 // Generates code snippets for opcode `Opcode`.
125 static llvm::Expected
<std::vector
<BenchmarkCode
>>
126 generateSnippets(const LLVMState
&State
, unsigned Opcode
) {
127 const std::unique_ptr
<SnippetGenerator
> Generator
=
128 State
.getExegesisTarget().createSnippetGenerator(BenchmarkMode
, State
);
130 llvm::report_fatal_error("cannot create snippet generator");
132 const llvm::MCInstrDesc
&InstrDesc
= State
.getInstrInfo().get(Opcode
);
133 // Ignore instructions that we cannot run.
134 if (InstrDesc
.isPseudo())
135 return llvm::make_error
<BenchmarkFailure
>("Unsupported opcode: isPseudo");
136 if (InstrDesc
.isBranch() || InstrDesc
.isIndirectBranch())
137 return llvm::make_error
<BenchmarkFailure
>(
138 "Unsupported opcode: isBranch/isIndirectBranch");
139 if (InstrDesc
.isCall() || InstrDesc
.isReturn())
140 return llvm::make_error
<BenchmarkFailure
>(
141 "Unsupported opcode: isCall/isReturn");
143 return Generator
->generateConfigurations(Opcode
);
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.
151 class BenchmarkCodeStreamer
: public llvm::MCStreamer
,
152 public llvm::AsmCommentConsumer
{
154 explicit BenchmarkCodeStreamer(llvm::MCContext
*Context
,
155 const llvm::MCRegisterInfo
*TheRegInfo
,
156 BenchmarkCode
*Result
)
157 : llvm::MCStreamer(*Context
), RegInfo(TheRegInfo
), Result(Result
) {}
159 // Implementation of the llvm::MCStreamer interface. We only care about
161 void EmitInstruction(const llvm::MCInst
&Instruction
,
162 const llvm::MCSubtargetInfo
&STI
,
163 bool PrintSchedInfo
) override
{
164 Result
->Instructions
.push_back(Instruction
);
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-"))
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
183 if (!(RegVal
.Register
= findRegisterByName(Parts
[0].trim()))) {
184 llvm::errs() << "unknown register in 'LLVM-EXEGESIS-DEFREG "
185 << CommentText
<< "\n";
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
));
195 if (CommentText
.consume_front("LIVEIN")) {
196 // LLVM-EXEGESIS-LIVEIN <reg>
197 if (unsigned Reg
= findRegisterByName(CommentText
.ltrim()))
198 Result
->LiveIns
.push_back(Reg
);
200 llvm::errs() << "unknown register in 'LLVM-EXEGESIS-LIVEIN "
201 << CommentText
<< "\n";
208 unsigned numInvalidComments() const { return InvalidComments
; }
211 // We only care about instructions, we don't implement this part of the API.
212 void EmitCommonSymbol(llvm::MCSymbol
*Symbol
, uint64_t Size
,
213 unsigned ByteAlignment
) override
{}
214 bool EmitSymbolAttribute(llvm::MCSymbol
*Symbol
,
215 llvm::MCSymbolAttr Attribute
) override
{
218 void EmitValueToAlignment(unsigned ByteAlignment
, int64_t Value
,
220 unsigned MaxBytesToEmit
) override
{}
221 void EmitZerofill(llvm::MCSection
*Section
, llvm::MCSymbol
*Symbol
,
222 uint64_t Size
, unsigned ByteAlignment
,
223 llvm::SMLoc Loc
) override
{}
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
))
231 llvm::errs() << "'" << RegName
232 << "' is not a valid register name for the target\n";
236 const llvm::MCRegisterInfo
*const RegInfo
;
237 BenchmarkCode
*const Result
;
238 unsigned InvalidComments
= 0;
243 // Reads code snippets from file `Filename`.
244 static llvm::Expected
<std::vector
<BenchmarkCode
>>
245 readSnippets(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());
253 SM
.AddNewSourceBuffer(std::move(BufferPtr
.get()), llvm::SMLoc());
255 BenchmarkCode Result
;
257 llvm::MCObjectFileInfo ObjectFileInfo
;
258 const llvm::TargetMachine
&TM
= State
.getTargetMachine();
259 llvm::MCContext
Context(TM
.getMCAsmInfo(), TM
.getMCRegisterInfo(),
261 ObjectFileInfo
.InitMCObjectFileInfo(TM
.getTargetTriple(), /*PIC*/ false,
263 BenchmarkCodeStreamer
Streamer(&Context
, TM
.getMCRegisterInfo(), &Result
);
264 const std::unique_ptr
<llvm::MCAsmParser
> AsmParser(
265 llvm::createMCAsmParser(SM
, Context
, Streamer
, *TM
.getMCAsmInfo()));
267 return llvm::make_error
<BenchmarkFailure
>("cannot create asm parser");
268 AsmParser
->getLexer().setCommentConsumer(&Streamer
);
270 const std::unique_ptr
<llvm::MCTargetAsmParser
> TargetAsmParser(
271 TM
.getTarget().createMCAsmParser(*TM
.getMCSubtargetInfo(), *AsmParser
,
272 *TM
.getMCInstrInfo(),
273 llvm::MCTargetOptions()));
275 if (!TargetAsmParser
)
276 return llvm::make_error
<BenchmarkFailure
>(
277 "cannot create target asm parser");
278 AsmParser
->setTargetParser(*TargetAsmParser
);
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
)};
290 void benchmarkMain() {
291 if (exegesis::pfm::pfmInitialize())
292 llvm::report_fatal_error("cannot initialize libpfm");
294 llvm::InitializeNativeTarget();
295 llvm::InitializeNativeTargetAsmPrinter();
296 llvm::InitializeNativeTargetAsmParser();
297 #ifdef LLVM_EXEGESIS_INITIALIZE_NATIVE_TARGET
298 LLVM_EXEGESIS_INITIALIZE_NATIVE_TARGET();
301 const LLVMState State
;
302 const auto Opcode
= getOpcodeOrDie(State
.getInstrInfo());
304 std::vector
<BenchmarkCode
> Configurations
;
306 // Ignore instructions without a sched class if -ignore-invalid-sched-class
308 if (IgnoreInvalidSchedClass
&&
309 State
.getInstrInfo().get(Opcode
).getSchedClass() == 0) {
310 llvm::errs() << "ignoring instruction without sched class\n";
313 Configurations
= ExitOnErr(generateSnippets(State
, Opcode
));
315 Configurations
= ExitOnErr(readSnippets(State
, SnippetsFile
));
318 const std::unique_ptr
<BenchmarkRunner
> Runner
=
319 State
.getExegesisTarget().createBenchmarkRunner(BenchmarkMode
, State
);
321 llvm::report_fatal_error("cannot create benchmark runner");
324 if (NumRepetitions
== 0)
325 llvm::report_fatal_error("--num-repetitions must be greater than zero");
327 // Write to standard output if file is not set.
328 if (BenchmarkFile
.empty())
331 for (const BenchmarkCode
&Conf
: Configurations
) {
332 InstructionBenchmark Result
=
333 Runner
->runConfiguration(Conf
, NumRepetitions
);
334 ExitOnErr(Result
.writeYaml(State
, BenchmarkFile
));
336 exegesis::pfm::pfmTerminate();
339 // Prints the results of running analysis pass `Pass` to file `OutputFilename`
340 // if OutputFilename is non-empty.
341 template <typename Pass
>
342 static void maybeRunAnalysis(const Analysis
&Analyzer
, const std::string
&Name
,
343 const std::string
&OutputFilename
) {
344 if (OutputFilename
.empty())
346 if (OutputFilename
!= "-") {
347 llvm::errs() << "Printing " << Name
<< " results to file '"
348 << OutputFilename
<< "'\n";
350 std::error_code ErrorCode
;
351 llvm::raw_fd_ostream
ClustersOS(OutputFilename
, ErrorCode
,
352 llvm::sys::fs::FA_Read
|
353 llvm::sys::fs::FA_Write
);
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
));
360 static void analysisMain() {
361 if (BenchmarkFile
.empty())
362 llvm::report_fatal_error("--benchmarks-file must be set.");
364 llvm::InitializeNativeTarget();
365 llvm::InitializeNativeTargetAsmPrinter();
366 llvm::InitializeNativeTargetDisassembler();
368 const LLVMState State
;
369 const std::vector
<InstructionBenchmark
> Points
=
370 ExitOnErr(InstructionBenchmark::readYamls(State
, BenchmarkFile
));
371 llvm::outs() << "Parsed " << Points
.size() << " benchmark points\n";
372 if (Points
.empty()) {
373 llvm::errs() << "no benchmarks to analyze\n";
376 // FIXME: Check that all points have the same triple/cpu.
377 // FIXME: Merge points from several runs (latency and uops).
380 const auto *TheTarget
=
381 llvm::TargetRegistry::lookupTarget(Points
[0].LLVMTriple
, Error
);
383 llvm::errs() << "unknown target '" << Points
[0].LLVMTriple
<< "'\n";
386 const auto Clustering
= ExitOnErr(InstructionBenchmarkClustering::create(
387 Points
, AnalysisNumPoints
, AnalysisEpsilon
));
389 const Analysis
Analyzer(*TheTarget
, Clustering
);
391 maybeRunAnalysis
<Analysis::PrintClusters
>(Analyzer
, "analysis clusters",
392 AnalysisClustersOutputFile
);
393 maybeRunAnalysis
<Analysis::PrintSchedClassInconsistencies
>(
394 Analyzer
, "sched class consistency analysis",
395 AnalysisInconsistenciesOutputFile
);
398 } // namespace exegesis
400 int main(int Argc
, char **Argv
) {
401 llvm::cl::ParseCommandLineOptions(Argc
, Argv
, "");
403 exegesis::ExitOnErr
.setExitCodeMapper([](const llvm::Error
&Err
) {
404 if (Err
.isA
<llvm::StringError
>())
409 if (BenchmarkMode
== exegesis::InstructionBenchmark::Unknown
) {
410 exegesis::analysisMain();
412 exegesis::benchmarkMain();