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 #include <unordered_map>
43 static llvm::cl::opt
<unsigned>
44 OpcodeIndex("opcode-index", llvm::cl::desc("opcode to measure, by index"),
47 static llvm::cl::opt
<std::string
>
48 OpcodeName("opcode-name", llvm::cl::desc("opcode to measure, by name"),
51 static llvm::cl::opt
<std::string
>
52 SnippetsFile("snippets-file", llvm::cl::desc("code snippets to measure"),
55 static llvm::cl::opt
<std::string
>
56 BenchmarkFile("benchmarks-file", llvm::cl::desc(""), llvm::cl::init(""));
58 static llvm::cl::opt
<exegesis::InstructionBenchmark::ModeE
> BenchmarkMode(
59 "mode", llvm::cl::desc("the mode to run"),
60 llvm::cl::values(clEnumValN(exegesis::InstructionBenchmark::Latency
,
61 "latency", "Instruction Latency"),
62 clEnumValN(exegesis::InstructionBenchmark::Uops
, "uops",
64 // When not asking for a specific benchmark mode, we'll
65 // analyse the results.
66 clEnumValN(exegesis::InstructionBenchmark::Unknown
,
67 "analysis", "Analysis")));
69 static llvm::cl::opt
<unsigned>
70 NumRepetitions("num-repetitions",
71 llvm::cl::desc("number of time to repeat the asm snippet"),
72 llvm::cl::init(10000));
74 static llvm::cl::opt
<bool> IgnoreInvalidSchedClass(
75 "ignore-invalid-sched-class",
76 llvm::cl::desc("ignore instructions that do not define a sched class"),
77 llvm::cl::init(false));
79 static llvm::cl::opt
<unsigned> AnalysisNumPoints(
81 llvm::cl::desc("minimum number of points in an analysis cluster"),
84 static llvm::cl::opt
<float>
85 AnalysisEpsilon("analysis-epsilon",
86 llvm::cl::desc("dbscan epsilon for analysis clustering"),
89 static llvm::cl::opt
<std::string
>
90 AnalysisClustersOutputFile("analysis-clusters-output-file",
91 llvm::cl::desc(""), llvm::cl::init("-"));
92 static llvm::cl::opt
<std::string
>
93 AnalysisInconsistenciesOutputFile("analysis-inconsistencies-output-file",
94 llvm::cl::desc(""), llvm::cl::init("-"));
98 static llvm::ExitOnError ExitOnErr
;
100 #ifdef LLVM_EXEGESIS_INITIALIZE_NATIVE_TARGET
101 void LLVM_EXEGESIS_INITIALIZE_NATIVE_TARGET();
104 // Checks that only one of OpcodeName, OpcodeIndex or SnippetsFile is provided,
105 // and returns the opcode index or 0 if snippets should be read from
107 static unsigned getOpcodeOrDie(const llvm::MCInstrInfo
&MCInstrInfo
) {
108 const size_t NumSetFlags
= (OpcodeName
.empty() ? 0 : 1) +
109 (OpcodeIndex
== 0 ? 0 : 1) +
110 (SnippetsFile
.empty() ? 0 : 1);
111 if (NumSetFlags
!= 1)
112 llvm::report_fatal_error(
113 "please provide one and only one of 'opcode-index', 'opcode-name' or "
115 if (!SnippetsFile
.empty())
119 // Resolve opcode name -> opcode.
120 for (unsigned I
= 0, E
= MCInstrInfo
.getNumOpcodes(); I
< E
; ++I
)
121 if (MCInstrInfo
.getName(I
) == OpcodeName
)
123 llvm::report_fatal_error(llvm::Twine("unknown opcode ").concat(OpcodeName
));
126 // Generates code snippets for opcode `Opcode`.
127 static llvm::Expected
<std::vector
<BenchmarkCode
>>
128 generateSnippets(const LLVMState
&State
, unsigned Opcode
) {
129 const std::unique_ptr
<SnippetGenerator
> Generator
=
130 State
.getExegesisTarget().createSnippetGenerator(BenchmarkMode
, State
);
132 llvm::report_fatal_error("cannot create snippet generator");
134 const llvm::MCInstrDesc
&InstrDesc
= State
.getInstrInfo().get(Opcode
);
135 // Ignore instructions that we cannot run.
136 if (InstrDesc
.isPseudo())
137 return llvm::make_error
<BenchmarkFailure
>("Unsupported opcode: isPseudo");
138 if (InstrDesc
.isBranch() || InstrDesc
.isIndirectBranch())
139 return llvm::make_error
<BenchmarkFailure
>(
140 "Unsupported opcode: isBranch/isIndirectBranch");
141 if (InstrDesc
.isCall() || InstrDesc
.isReturn())
142 return llvm::make_error
<BenchmarkFailure
>(
143 "Unsupported opcode: isCall/isReturn");
145 return Generator
->generateConfigurations(Opcode
);
150 // An MCStreamer that reads a BenchmarkCode definition from a file.
151 // The BenchmarkCode definition is just an asm file, with additional comments to
152 // specify which registers should be defined or are live on entry.
153 class BenchmarkCodeStreamer
: public llvm::MCStreamer
,
154 public llvm::AsmCommentConsumer
{
156 explicit BenchmarkCodeStreamer(llvm::MCContext
*Context
,
157 const llvm::MCRegisterInfo
*TheRegInfo
,
158 BenchmarkCode
*Result
)
159 : llvm::MCStreamer(*Context
), RegInfo(TheRegInfo
), Result(Result
) {}
161 // Implementation of the llvm::MCStreamer interface. We only care about
163 void EmitInstruction(const llvm::MCInst
&instruction
,
164 const llvm::MCSubtargetInfo
&mc_subtarget_info
,
165 bool PrintSchedInfo
) override
{
166 Result
->Instructions
.push_back(instruction
);
169 // Implementation of the llvm::AsmCommentConsumer.
170 void HandleComment(llvm::SMLoc Loc
, llvm::StringRef CommentText
) override
{
171 CommentText
= CommentText
.trim();
172 if (!CommentText
.consume_front("LLVM-EXEGESIS-"))
174 if (CommentText
.consume_front("DEFREG")) {
175 // LLVM-EXEGESIS-DEFREF <reg> <hex_value>
176 RegisterValue RegVal
;
177 llvm::SmallVector
<llvm::StringRef
, 2> Parts
;
178 CommentText
.split(Parts
, ' ', /*unlimited splits*/ -1,
179 /*do not keep empty strings*/ false);
180 if (Parts
.size() != 2) {
181 llvm::errs() << "invalid comment 'LLVM-EXEGESIS-DEFREG " << CommentText
185 if (!(RegVal
.Register
= findRegisterByName(Parts
[0].trim()))) {
186 llvm::errs() << "unknown register in 'LLVM-EXEGESIS-DEFREG "
187 << CommentText
<< "\n";
191 const llvm::StringRef HexValue
= Parts
[1].trim();
192 RegVal
.Value
= llvm::APInt(
193 /* each hex digit is 4 bits */ HexValue
.size() * 4, HexValue
, 16);
194 Result
->RegisterInitialValues
.push_back(std::move(RegVal
));
197 if (CommentText
.consume_front("LIVEIN")) {
198 // LLVM-EXEGESIS-LIVEIN <reg>
199 if (unsigned Reg
= findRegisterByName(CommentText
.ltrim()))
200 Result
->LiveIns
.push_back(Reg
);
202 llvm::errs() << "unknown register in 'LLVM-EXEGESIS-LIVEIN "
203 << CommentText
<< "\n";
210 unsigned numInvalidComments() const { return InvalidComments
; }
213 // We only care about instructions, we don't implement this part of the API.
214 void EmitCommonSymbol(llvm::MCSymbol
*symbol
, uint64_t size
,
215 unsigned byte_alignment
) override
{}
216 bool EmitSymbolAttribute(llvm::MCSymbol
*symbol
,
217 llvm::MCSymbolAttr attribute
) override
{
220 void EmitValueToAlignment(unsigned byte_alignment
, int64_t value
,
222 unsigned max_bytes_to_emit
) override
{}
223 void EmitZerofill(llvm::MCSection
*section
, llvm::MCSymbol
*symbol
,
224 uint64_t size
, unsigned byte_alignment
,
225 llvm::SMLoc Loc
) override
{}
227 unsigned findRegisterByName(const llvm::StringRef RegName
) const {
228 // FIXME: Can we do better than this ?
229 for (unsigned I
= 0, E
= RegInfo
->getNumRegs(); I
< E
; ++I
) {
230 if (RegName
== RegInfo
->getName(I
))
233 llvm::errs() << "'" << RegName
234 << "' is not a valid register name for the target\n";
238 const llvm::MCRegisterInfo
*const RegInfo
;
239 BenchmarkCode
*const Result
;
240 unsigned InvalidComments
= 0;
245 // Reads code snippets from file `Filename`.
246 static llvm::Expected
<std::vector
<BenchmarkCode
>>
247 readSnippets(const LLVMState
&State
, llvm::StringRef Filename
) {
248 llvm::ErrorOr
<std::unique_ptr
<llvm::MemoryBuffer
>> BufferPtr
=
249 llvm::MemoryBuffer::getFileOrSTDIN(Filename
);
250 if (std::error_code EC
= BufferPtr
.getError()) {
251 return llvm::make_error
<BenchmarkFailure
>(
252 "cannot read snippet: " + Filename
+ ": " + EC
.message());
255 SM
.AddNewSourceBuffer(std::move(BufferPtr
.get()), llvm::SMLoc());
257 BenchmarkCode Result
;
259 llvm::MCObjectFileInfo ObjectFileInfo
;
260 const llvm::TargetMachine
&TM
= State
.getTargetMachine();
261 llvm::MCContext
Context(TM
.getMCAsmInfo(), TM
.getMCRegisterInfo(),
263 ObjectFileInfo
.InitMCObjectFileInfo(TM
.getTargetTriple(), /*PIC*/ false,
265 BenchmarkCodeStreamer
Streamer(&Context
, TM
.getMCRegisterInfo(), &Result
);
266 const std::unique_ptr
<llvm::MCAsmParser
> AsmParser(
267 llvm::createMCAsmParser(SM
, Context
, Streamer
, *TM
.getMCAsmInfo()));
269 return llvm::make_error
<BenchmarkFailure
>("cannot create asm parser");
270 AsmParser
->getLexer().setCommentConsumer(&Streamer
);
272 const std::unique_ptr
<llvm::MCTargetAsmParser
> TargetAsmParser(
273 TM
.getTarget().createMCAsmParser(*TM
.getMCSubtargetInfo(), *AsmParser
,
274 *TM
.getMCInstrInfo(),
275 llvm::MCTargetOptions()));
277 if (!TargetAsmParser
)
278 return llvm::make_error
<BenchmarkFailure
>(
279 "cannot create target asm parser");
280 AsmParser
->setTargetParser(*TargetAsmParser
);
282 if (AsmParser
->Run(false))
283 return llvm::make_error
<BenchmarkFailure
>("cannot parse asm file");
284 if (Streamer
.numInvalidComments())
285 return llvm::make_error
<BenchmarkFailure
>(
286 llvm::Twine("found ")
287 .concat(llvm::Twine(Streamer
.numInvalidComments()))
288 .concat(" invalid LLVM-EXEGESIS comments"));
289 return std::vector
<BenchmarkCode
>{std::move(Result
)};
292 void benchmarkMain() {
293 if (exegesis::pfm::pfmInitialize())
294 llvm::report_fatal_error("cannot initialize libpfm");
296 llvm::InitializeNativeTarget();
297 llvm::InitializeNativeTargetAsmPrinter();
298 llvm::InitializeNativeTargetAsmParser();
299 #ifdef LLVM_EXEGESIS_INITIALIZE_NATIVE_TARGET
300 LLVM_EXEGESIS_INITIALIZE_NATIVE_TARGET();
303 const LLVMState State
;
304 const auto Opcode
= getOpcodeOrDie(State
.getInstrInfo());
306 std::vector
<BenchmarkCode
> Configurations
;
308 // Ignore instructions without a sched class if -ignore-invalid-sched-class
310 if (IgnoreInvalidSchedClass
&&
311 State
.getInstrInfo().get(Opcode
).getSchedClass() == 0) {
312 llvm::errs() << "ignoring instruction without sched class\n";
315 Configurations
= ExitOnErr(generateSnippets(State
, Opcode
));
317 Configurations
= ExitOnErr(readSnippets(State
, SnippetsFile
));
320 const std::unique_ptr
<BenchmarkRunner
> Runner
=
321 State
.getExegesisTarget().createBenchmarkRunner(BenchmarkMode
, State
);
323 llvm::report_fatal_error("cannot create benchmark runner");
326 if (NumRepetitions
== 0)
327 llvm::report_fatal_error("--num-repetitions must be greater than zero");
329 // Write to standard output if file is not set.
330 if (BenchmarkFile
.empty())
333 for (const BenchmarkCode
&Conf
: Configurations
) {
334 InstructionBenchmark Result
=
335 Runner
->runConfiguration(Conf
, NumRepetitions
);
336 ExitOnErr(Result
.writeYaml(State
, BenchmarkFile
));
338 exegesis::pfm::pfmTerminate();
341 // Prints the results of running analysis pass `Pass` to file `OutputFilename`
342 // if OutputFilename is non-empty.
343 template <typename Pass
>
344 static void maybeRunAnalysis(const Analysis
&Analyzer
, const std::string
&Name
,
345 const std::string
&OutputFilename
) {
346 if (OutputFilename
.empty())
348 if (OutputFilename
!= "-") {
349 llvm::errs() << "Printing " << Name
<< " results to file '"
350 << OutputFilename
<< "'\n";
352 std::error_code ErrorCode
;
353 llvm::raw_fd_ostream
ClustersOS(OutputFilename
, ErrorCode
,
354 llvm::sys::fs::FA_Read
|
355 llvm::sys::fs::FA_Write
);
357 llvm::report_fatal_error("cannot open out file: " + OutputFilename
);
358 if (auto Err
= Analyzer
.run
<Pass
>(ClustersOS
))
359 llvm::report_fatal_error(std::move(Err
));
362 static void analysisMain() {
363 if (BenchmarkFile
.empty())
364 llvm::report_fatal_error("--benchmarks-file must be set.");
366 llvm::InitializeNativeTarget();
367 llvm::InitializeNativeTargetAsmPrinter();
368 llvm::InitializeNativeTargetDisassembler();
370 const LLVMState State
;
371 const std::vector
<InstructionBenchmark
> Points
=
372 ExitOnErr(InstructionBenchmark::readYamls(State
, BenchmarkFile
));
373 llvm::outs() << "Parsed " << Points
.size() << " benchmark points\n";
374 if (Points
.empty()) {
375 llvm::errs() << "no benchmarks to analyze\n";
378 // FIXME: Check that all points have the same triple/cpu.
379 // FIXME: Merge points from several runs (latency and uops).
382 const auto *TheTarget
=
383 llvm::TargetRegistry::lookupTarget(Points
[0].LLVMTriple
, Error
);
385 llvm::errs() << "unknown target '" << Points
[0].LLVMTriple
<< "'\n";
388 const auto Clustering
= ExitOnErr(InstructionBenchmarkClustering::create(
389 Points
, AnalysisNumPoints
, AnalysisEpsilon
));
391 const Analysis
Analyzer(*TheTarget
, Clustering
);
393 maybeRunAnalysis
<Analysis::PrintClusters
>(Analyzer
, "analysis clusters",
394 AnalysisClustersOutputFile
);
395 maybeRunAnalysis
<Analysis::PrintSchedClassInconsistencies
>(
396 Analyzer
, "sched class consistency analysis",
397 AnalysisInconsistenciesOutputFile
);
400 } // namespace exegesis
402 int main(int Argc
, char **Argv
) {
403 llvm::cl::ParseCommandLineOptions(Argc
, Argv
, "");
405 exegesis::ExitOnErr
.setExitCodeMapper([](const llvm::Error
&Err
) {
406 if (Err
.isA
<llvm::StringError
>())
411 if (BenchmarkMode
== exegesis::InstructionBenchmark::Unknown
) {
412 exegesis::analysisMain();
414 exegesis::benchmarkMain();