• LLVM学习笔记(57)


    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(&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 YamlFile;

    329       if (RemarksFilename != "") {

    330          std::error_code EC;

    331         YamlFile =

    332                llvm::make_unique(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(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/*HasJIT=*/true>

    25           X(getTheX86_32Target(), "x86", "32-bit X86: Pentium-Pro and above");

    26      

    27         RegisterTarget/*HasJIT=*/true>

    28           Y(getTheX86_64Target(), "x86-64", "64-bit X86: EM64T and AMD64");

    29       }

    V7.0TheX86_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 X(TheX86_32Target);

    78         RegisterTargetMachine 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.0LVMInitializeX86Target()里额外初始化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需要这些遍:IRTranslatorLegalizerLocalizer(将常量形式指令移动/复制到靠近使用处。主要是为了绕过快速寄存器分配器的缺陷)、RegBankSelect,以及我们已经有点熟悉的InstructionSelect

    剩下的遍有这些(WinEHStatePassWindows相关,我们不看):

    • FixupBWInstPass:查找使用32位指令替换64位指令的机会。这样做,一方面能消除64位寄存器不使用的高位带来的伪依赖;另一方面能减小代码大小。
    • EvexToVexInstPass:遍历使用使用EXE前缀编码的AVX-512指令,如果可能根据VEX编码替换它们,VEX编码能少2字节。
    • FixupLEAPass:查找能被重写为LEA指令的指令,以减小流水线时延。
    • ShadowCallStackPass:检查函数prologs/epilogs,确定在该函数执行期间,返回地址没有被毁坏。
    • X86CallFrameOptimizationPass:将函数参数到栈的mov,转换为push。因为两个主要原因这是有利的:
    1. Push指令的编码远小于基于栈指针的mov
    2. 有可能直接push内存实参。因此,如果这个转换在寄存器分配前执行,它有助于减轻寄存器压力。
    • X86CmovConverterPass:在有利时,将X86 cmov指令转换为分支。
    • X86ExecutionDomainFixPass:某些X86 SSE指令,如movandorxor对不同的操作数类型有不同的变形可用。这些变形是等价的,但在Nehalem及更新的CPU上,在整数域与浮点域之间传输数据有额外时延。这个遍尽量减少域跨越。
    • X86DomainReassignmentPass:尝试找出在一个域中的指令链(闭包),如果有利,将它们转换为不同域里等价的指令。
    • X86AvoidSFBPass:如果一个load跟着一个stroe后面,且重新载入这个store写入内存的数据,Intel微架构在许多情形里可以将这个数据从store直接转发给load。这个“store转发”通过使得 load可以直接获得数据,无需访问缓存或内存,节省了周期。

    store转发阻塞”出现在store不能被转发到load的情形里。Store转发阻塞在Intel Core微架构上最常见,在这个架构上小的store不能转发给大的load。有个store转发阻塞的惩罚大约是13个周期。

    在将memcpy调用降级为一系列loadstore时,这个遍尝试识别以及处理由编译器产生“store转发阻塞”的情形。

    • X86FlagsCopyLoweringPass:通过直接提前以及保留单独的标志位,降级EFLAGSCOPY节点。

    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 X(TheX86_32Target);

    705       RegisterAsmPrinter Y(TheX86_64Target);

    706     }

    InitializeAllAsmParser()调用的LLVMInitializeX86AsmPrinter()通过RegisterAsmPrinter的构造函数将X86AsmPrinter()注册为TheX86_32Target及TheX86_64Target的汇编输出器。

    3449   extern "C" void LLVMInitializeX86AsmParser() {

    3450     RegisterMCAsmParser X(TheX86_32Target);

    3451     RegisterMCAsmParser Y(TheX86_64Target);

    3452   }

    方法LLVMInitializeX86AsmParser()将X86AsmParser()注册为TheX86_32Target及TheX86_64Target的汇编解析器。

    Main()的293~307行注册与代码生成及IR相关的遍。我们稍后一点再来看。

  • 相关阅读:
    Tomcat在CentOS上的安装部署
    ardupilot开发 --- RTSP视频流 篇
    【docker】docker 、docker-compose离线安装
    新来的一个同事,把SpringBoot参数校验玩的那叫一个优雅
    WuThreat身份安全云-TVD每日漏洞情报-2023-10-10
    数字化时代的探索,企业如何做好数字化转型?
    Pip版本问题导致Python模块安装失败
    洛谷 P3128 最大流Max Flow
    腾讯云服务器多少钱一年?2023年腾讯云优惠云服务器推荐
    npm yarn 和 pnpm 之间命令的区别
  • 原文地址:https://blog.csdn.net/wuhui_gdnt/article/details/134075108