4.2. 代码入口(以下为7.0代码)
LLVM有两个编译器。一个是静态编译器llc——它的输入是Clang从C、C++及ObjC源代码转换而来的LLVM IR,把IR编译为LLVM的字节码,或指定目标机器的汇编或机器码。另一个是动态编译器lli——它的输入是LLVM的字节码,并执行之。显然,指令选择与调度与llc的关系要紧密得多。文件llc.cpp就是llc的源代码所在。Llc的入口就是其中的main()函数。大致上这个main()函数是这样的:
276 int main(int argc, char **argv) {
277 InitLLVM X(argc, argv);
278
279 // Enable debug stream buffering.
280 EnableDebugBuffering = true;
281
282 LLVMContext Context;
283
284 // Initialize targets first, so that --version shows registered targets.
285 InitializeAllTargets();
286 InitializeAllTargetMCs();
287 InitializeAllAsmPrinters();
288 InitializeAllAsmParsers();
289
290 // Initialize codegen and IR passes used by llc so that the -print-after,
291 // -print-before, and -stop-after options work.
292 PassRegistry *Registry = PassRegistry::getPassRegistry();
293 initializeCore(*Registry);
294 initializeCodeGen(*Registry);
295 initializeLoopStrengthReducePass(*Registry);
296 initializeLowerIntrinsicsPass(*Registry);
297 initializeEntryExitInstrumenterPass(*Registry);
298 initializePostInlineEntryExitInstrumenterPass(*Registry);
299 initializeUnreachableBlockElimPass(*Registry);
300 initializeConstantHoistingLegacyPassPass(*Registry);
301 initializeScalarOpts(*Registry);
302 initializeVectorization(*Registry);
303 initializeScalarizeMaskedMemIntrinPass(*Registry);
304 initializeExpandReductionsPass(*Registry);
305
306 // Initialize debugging passes.
307 initializeScavengerTestPass(*Registry);
308
309 // Register the target printer for --version.
310 cl::AddExtraVersionPrinter(TargetRegistry::printRegisteredTargetsForVersion);
311
312 cl::ParseCommandLineOptions(argc, argv, "llvm system compiler\n")
313
314 Context.setDiscardValueNames(DiscardValueNames);
315
316 // Set a diagnostic handler that doesn't exit on the first error
317 bool HasError = false;
318 Context.setDiagnosticHandler(
319 llvm::make_unique<LLCDiagnosticHandler>(&HasError));
320 Context.setInlineAsmDiagnosticHandler(InlineAsmDiagHandler, &HasError);
321
322 if (PassRemarksWithHotness)
323 Context.setDiagnosticsHotnessRequested(true);
324
325 if (PassRemarksHotnessThreshold)
326 Context.setDiagnosticsHotnessThreshold(PassRemarksHotnessThreshold);
327
328 std::unique_ptr<ToolOutputFile> YamlFile;
329 if (RemarksFilename != "") {
330 std::error_code EC;
331 YamlFile =
332 llvm::make_unique<ToolOutputFile>(RemarksFilename, EC, sys::fs::F_None);
333 if (EC) {
334 WithColor::error(errs(), argv[0]) << EC.message() << '\n';
335 return 1;
336 }
337 Context.setDiagnosticsOutputFile(
338 llvm::make_unique<yaml::Output>(YamlFile->os()));
339 }
340
341 if (InputLanguage != "" && InputLanguage != "ir" &&
342 InputLanguage != "mir") {
343 WithColor::error(errs(), argv[0])
344 << "input language must be '', 'IR' or 'MIR'\n";
345 return 1;
346 }
347
348 // Compile the module TimeCompilations times to give better compile time
349 // metrics.
350 for (unsigned I = TimeCompilations; I; --I)
351 if (int RetVal = compileModule(argv, Context))
352 return RetVal;
353
354 if (YamlFile)
355 YamlFile->keep();
356 return 0;
357 }
4.3. 目标机器的初始化方法
285~288行初始化所有与目标机器相关的模块。以X86目标机器为例,InitializeAllTargets()会依次调用LLVMInitializeX86TargetInfo()以及LLVMInitializeX86Targe()(这个函数实际上会调用所有LLVM支持的目标机器的这些方法):
14 Target &llvm::getTheX86_32Target() { ß v7.0增加
15 static Target TheX86_32Target;
16 return TheX86_32Target;
17 }
18 Target &llvm::getTheX86_64Target() {
19 static Target TheX86_64Target;
20 return TheX86_64Target;
21 }
22
23 extern "C" void LLVMInitializeX86TargetInfo() {
24 RegisterTarget<Triple::x86, /*HasJIT=*/true>
25 X(getTheX86_32Target(), "x86", "32-bit X86: Pentium-Pro and above");
26
27 RegisterTarget<Triple::x86_64, /*HasJIT=*/true>
28 Y(getTheX86_64Target(), "x86-64", "64-bit X86: EM64T and AMD64");
29 }
V7.0将TheX86_32Target/TheX86_64Target声明为函数的静态成员,通过函数获取,避免了无意的修改。
RegisterTarget在构造函数里调用方法TargetRegistry::RegisterTarget()注册代表32位与64位X86目标机器的Target实例。这使得在后面可以通过17或20行的Triple值来查找这些Target对象。
75 extern "C" void LLVMInitializeX86Target() {
76 // Register the target.
77 RegisterTargetMachine<X86TargetMachine> X(TheX86_32Target);
78 RegisterTargetMachine<X86TargetMachine> Y(TheX86_64Target);
79
80 PassRegistry &PR = *PassRegistry::getPassRegistry();
81 initializeGlobalISel(PR);
82 initializeWinEHStatePassPass(PR);
83 initializeFixupBWInstPassPass(PR);
84 initializeEvexToVexInstPassPass(PR);
85 initializeFixupLEAPassPass(PR);
86 initializeShadowCallStackPass(PR);
87 initializeX86CallFrameOptimizationPass(PR);
88 initializeX86CmovConverterPassPass(PR);
89 initializeX86ExecutionDomainFixPass(PR);
90 initializeX86DomainReassignmentPass(PR);
91 initializeX86AvoidSFBPassPass(PR);
92 initializeX86FlagsCopyLoweringPassPass(PR);
93 }
RegisterTargetMachine在构造函数里调用方法TargetRegistry::RegisterTargetMachine()将自己的Allocator()方法设置为TheX86_32Target及TheX86_64Target生成目标机器实现(X86TargetMachine)的构造方法指针TargetMachineCtorFn,为后面创建目标机器对象做好准备。
1102 template <class TargetMachineImpl> struct RegisterTargetMachine {
1103 RegisterTargetMachine(Target &T) {
1104 TargetRegistry::RegisterTargetMachine(T, &Allocator);
1105 }
1106
1107 private:
1108 static TargetMachine *
1109 Allocator(const Target &T, const Triple &TT, StringRef CPU, StringRef FS,
1110 const TargetOptions &Options, Reloc::Model RM,
1111 CodeModel::Model CM, CodeGenOpt::Level OL, bool JIT) {
1112 return new TargetMachineImpl(T, TT, CPU, FS, Options, RM, CM, OL, JIT);
1113 }
1114 };
上面1104行的TargetRegistry::RegisterTargetMachine()是同一文件里的一个静态函数:
756 static void RegisterTargetMachine(Target &T, Target::TargetMachineCtorTy Fn) {
757 T.TargetMachineCtorFn = Fn;
758 }
V7.0在LVMInitializeX86Target()里额外初始化x86所需的优化/处理遍。第一个就是GlobalISel相关的遍,我们看一下它要什么:
19 void llvm::initializeGlobalISel(PassRegistry &Registry) {
20 initializeIRTranslatorPass(Registry);
21 initializeLegalizerPass(Registry);
22 initializeLocalizerPass(Registry);
23 initializeRegBankSelectPass(Registry);
24 initializeInstructionSelectPass(Registry);
25 }
不出意外,GlobalISel需要这些遍:IRTranslator、Legalizer、Localizer(将常量形式指令移动/复制到靠近使用处。主要是为了绕过快速寄存器分配器的缺陷)、RegBankSelect,以及我们已经有点熟悉的InstructionSelect。
剩下的遍有这些(WinEHStatePass与Windows相关,我们不看):
- FixupBWInstPass:查找使用32位指令替换64位指令的机会。这样做,一方面能消除64位寄存器不使用的高位带来的伪依赖;另一方面能减小代码大小。
- EvexToVexInstPass:遍历使用使用EXE前缀编码的AVX-512指令,如果可能根据VEX编码替换它们,VEX编码能少2字节。
- FixupLEAPass:查找能被重写为LEA指令的指令,以减小流水线时延。
- ShadowCallStackPass:检查函数prologs/epilogs,确定在该函数执行期间,返回地址没有被毁坏。
- X86CallFrameOptimizationPass:将函数参数到栈的mov,转换为push。因为两个主要原因这是有利的:
- Push指令的编码远小于基于栈指针的mov。
- 有可能直接push内存实参。因此,如果这个转换在寄存器分配前执行,它有助于减轻寄存器压力。
- X86CmovConverterPass:在有利时,将X86 cmov指令转换为分支。
- X86ExecutionDomainFixPass:某些X86 SSE指令,如mov、and、or、xor对不同的操作数类型有不同的变形可用。这些变形是等价的,但在Nehalem及更新的CPU上,在整数域与浮点域之间传输数据有额外时延。这个遍尽量减少域跨越。
- X86DomainReassignmentPass:尝试找出在一个域中的指令链(闭包),如果有利,将它们转换为不同域里等价的指令。
- X86AvoidSFBPass:如果一个load跟着一个stroe后面,且重新载入这个store写入内存的数据,Intel微架构在许多情形里可以将这个数据从store直接转发给load。这个“store转发”通过使得 load可以直接获得数据,无需访问缓存或内存,节省了周期。
“store转发阻塞”出现在store不能被转发到load的情形里。Store转发阻塞在Intel Core微架构上最常见,在这个架构上小的store不能转发给大的load。有个store转发阻塞的惩罚大约是13个周期。
在将memcpy调用降级为一系列load与store时,这个遍尝试识别以及处理由编译器产生“store转发阻塞”的情形。
- X86FlagsCopyLoweringPass:通过直接提前以及保留单独的标志位,降级EFLAGS的COPY节点。
InitializeAllTargetMCs()调用LLVMInitializeX86TargetMC()执行与目标机器MC相关方法的注册(记录在对应的Target对象里,比如下面的TheX86_32Target与TheX86_64Target)。
449 extern "C" void LLVMInitializeX86TargetMC() {
450 for (Target *T: {&getTheX86_32Target(), &getTheX86_64Target()}) {
451 // Register the MC asm info.
452 RegisterMCAsmInfoFn X(*T, createX86MCAsmInfo);
453
454 // Register the MC instruction info.
455 TargetRegistry::RegisterMCInstrInfo(*T, createX86MCInstrInfo);
456
457 // Register the MC register info.
458 TargetRegistry::RegisterMCRegInfo(*T, createX86MCRegisterInfo);
459
460 // Register the MC subtarget info.
461 TargetRegistry::RegisterMCSubtargetInfo(*T,
462 X86_MC::createX86MCSubtargetInfo);
463
464 // Register the MC instruction analyzer.
465 TargetRegistry::RegisterMCInstrAnalysis(*T, createX86MCInstrAnalysis);
466
467 // Register the code emitter.
468 TargetRegistry::RegisterMCCodeEmitter(*T, createX86MCCodeEmitter);
469
470 // Register the obj target streamer.
471 TargetRegistry::RegisterObjectTargetStreamer(*T,
472 createX86ObjectTargetStreamer);
473
474 // Register the asm target streamer.
475 TargetRegistry::RegisterAsmTargetStreamer(*T, createX86AsmTargetStreamer);
476
477 TargetRegistry::RegisterCOFFStreamer(*T, createX86WinCOFFStreamer);
478
479 // Register the MCInstPrinter.
480 TargetRegistry::RegisterMCInstPrinter(*T, createX86MCInstPrinter);
481
482 // Register the MC relocation info.
483 TargetRegistry::RegisterMCRelocationInfo(*T, createX86MCRelocationInfo);
484 }
485
486 // Register the asm backend.
487 TargetRegistry::RegisterMCAsmBackend(TheX86_32Target,
488 createX86_32AsmBackend);
489 TargetRegistry::RegisterMCAsmBackend(TheX86_64Target,
490 createX86_64AsmBackend);
491 }
其中255行的createX86MCInstrInfo()调用InitX86MCInstrInfo(),258行的createX86MCRegisterInfo()会调用InitX86MCRegisterInfo(),462行的createX86MCSubtargetInfo()会调用InitX86MCSubtargetInfo()。这三个函数是在前面由TableGen根据.td文件生成。
InitializeAllAsmPrinters()类似地对X86目标机器调用下面的方法:
703 extern "C" void LLVMInitializeX86AsmPrinter() {
704 RegisterAsmPrinter<X86AsmPrinter> X(TheX86_32Target);
705 RegisterAsmPrinter<X86AsmPrinter> Y(TheX86_64Target);
706 }
InitializeAllAsmParser()调用的LLVMInitializeX86AsmPrinter()通过RegisterAsmPrinter的构造函数将X86AsmPrinter()注册为TheX86_32Target及TheX86_64Target的汇编输出器。
3449 extern "C" void LLVMInitializeX86AsmParser() {
3450 RegisterMCAsmParser<X86AsmParser> X(TheX86_32Target);
3451 RegisterMCAsmParser<X86AsmParser> Y(TheX86_64Target);
3452 }
方法LLVMInitializeX86AsmParser()将X86AsmParser()注册为TheX86_32Target及TheX86_64Target的汇编解析器。文章来源:https://www.toymoban.com/news/detail-737737.html
Main()的293~307行注册与代码生成及IR相关的遍。我们稍后一点再来看。文章来源地址https://www.toymoban.com/news/detail-737737.html
到了这里,关于LLVM学习笔记(57)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!