LLVM Pass从添加到执行的过程

3,654 阅读4分钟

clang的过渡

要想搞清楚Pass添加和执行的过程首先要清楚如何从编译前段的clang过渡到LLVM。

当AST构建完成之后交给ASTConsumer的子类BackendConsumer处理。首先将会通过CodeGenerator来完成从AST到IR的转换。转换完成之后调用HandleTranslationUnit

EmbedBitcode(getModule(), CodeGenOpts, llvm::MemoryBufferRef());

EmitBackendOutput(Diags, HeaderSearchOpts, CodeGenOpts, TargetOpts,
LangOpts, C.getTargetInfo().getDataLayout(),
getModule(), Action, std::move(AsmOutStream));

这里会调用两个函数,EmbedBitcode用于处理 -fembed-bitcode 参数,目的是用于在生成的obj文件中增加一个用于存放bitcode的section。

EmitBackendOutput定义位于BackendUtil.cpp中。函数中会定义一个AsmHelper,并且调用EmitAssembly或者EmitAssemblyWithNewPassManager

if (CGOpts.ExperimentalNewPassManager)
AsmHelper.EmitAssemblyWithNewPassManager(Action, std::move(OS));
else
AsmHelper.EmitAssembly(Action, std::move(OS));

BackendUtil.cpp文件仍然位于clang中,但是这里离LLVM已经相当接近了

重点看EmitAssembly

legacy::PassManager PerModulePasses;
PerModulePasses.add(
createTargetTransformInfoWrapperPass(getTargetIRAnalysis()));

legacy::FunctionPassManager PerFunctionPasses(TheModule);
PerFunctionPasses.add(
createTargetTransformInfoWrapperPass(getTargetIRAnalysis()));

CreatePasses(PerModulePasses, PerFunctionPasses);

这里生成两个PassManager,一个管理ModulePass,一个管理FunctionPass。CreatePasses中完成两个PassManager队与Pass的添加管理。

除了PerModulePasses和PerFunctionPasses之外还有一个CodeGenPasses,根据CodeGenOpts的编译要求来添加用于指令转换、生成的Pass

legacy::PassManager CodeGenPasses;
CodeGenPasses.add(
createTargetTransformInfoWrapperPass(getTargetIRAnalysis()));

std::unique_ptr<raw_fd_ostream> ThinLinkOS;

switch (Action) {
case Backend_EmitNothing:
break;

case Backend_EmitBC:
if (CodeGenOpts.EmitSummaryIndex) {
if (!CodeGenOpts.ThinLinkBitcodeFile.empty()) {
std::error_code EC;
ThinLinkOS.reset(new llvm::raw_fd_ostream(
CodeGenOpts.ThinLinkBitcodeFile, EC,
llvm::sys::fs::F_None));
if (EC) {
Diags.Report(diag::err_fe_unable_to_open_output) << CodeGenOpts.ThinLinkBitcodeFile
<< EC.message();
return;
}
}
PerModulePasses.add(
createWriteThinLTOBitcodePass(*OS, ThinLinkOS.get()));
}
else
PerModulePasses.add(
createBitcodeWriterPass(*OS, CodeGenOpts.EmitLLVMUseLists));
break;

case Backend_EmitLL:
PerModulePasses.add(
createPrintModulePass(*OS, "", CodeGenOpts.EmitLLVMUseLists));
break;

default:
if (!AddEmitPasses(CodeGenPasses, Action, *OS))
return;
}

当Pass添加完成就开始逐个启动

{
PrettyStackTraceString CrashInfo("Per-function optimization");

PerFunctionPasses.doInitialization();
for (Function &F : *TheModule)
if (!F.isDeclaration())
PerFunctionPasses.run(F);
PerFunctionPasses.doFinalization();
}

{
PrettyStackTraceString CrashInfo("Per-module optimization passes");
PerModulePasses.run(*TheModule);
}

{
PrettyStackTraceString CrashInfo("Code generation");
CodeGenPasses.run(*TheModule);
}

当Pass开始执行也就正式进入LLVM代码的范畴,执行完成后再返回到clang。

PassManagerBuilder与addExtension

回头再看CreatePasses中的内容,首先是有一个将PassManagerBuilder与LangOptions和CGOpts封装在一起的wrapper

// We need this wrapper to access LangOpts and CGOpts from extension functions
// that we add to the PassManagerBuilder.
class PassManagerBuilderWrapper : public PassManagerBuilder {
public:
PassManagerBuilderWrapper(const Triple &TargetTriple,
const CodeGenOptions &CGOpts,
const LangOptions &LangOpts)
: PassManagerBuilder(), TargetTriple(TargetTriple), CGOpts(CGOpts),
LangOpts(LangOpts) {}
const Triple &getTargetTriple() const { return TargetTriple; }
const CodeGenOptions &getCGOpts() const { return CGOpts; }
const LangOptions &getLangOpts() const { return LangOpts; }

private:
const Triple &TargetTriple;
const CodeGenOptions &CGOpts;
const LangOptions &LangOpts;
};
}

CreatePasses函数中做的首要事情就是生成一个PassManagerBuilder并将CGOpts和LangOpts中的内容绑定到一起

PassManagerBuilderWrapper PMBuilder(TargetTriple, CodeGenOpts, LangOpts);

// At O0 and O1 we only run the always inliner which is more efficient. At
// higher optimization levels we run the normal inliner.
if (CodeGenOpts.OptimizationLevel <= 1) {
bool InsertLifetimeIntrinsics = (CodeGenOpts.OptimizationLevel != 0 &&
!CodeGenOpts.DisableLifetimeMarkers);
PMBuilder.Inliner = createAlwaysInlinerLegacyPass(InsertLifetimeIntrinsics);
} else {
// We do not want to inline hot callsites for SamplePGO module-summary build
// because profile annotation will happen again in ThinLTO backend, and we
// want the IR of the hot path to match the profile.
PMBuilder.Inliner = createFunctionInliningPass(
CodeGenOpts.OptimizationLevel, CodeGenOpts.OptimizeSize,
(!CodeGenOpts.SampleProfileFile.empty() &&
CodeGenOpts.EmitSummaryIndex));
}

PMBuilder.OptLevel = CodeGenOpts.OptimizationLevel;
PMBuilder.SizeLevel = CodeGenOpts.OptimizeSize;
PMBuilder.SLPVectorize = CodeGenOpts.VectorizeSLP;
PMBuilder.LoopVectorize = CodeGenOpts.VectorizeLoop;

PMBuilder.DisableUnrollLoops = !CodeGenOpts.UnrollLoops;
PMBuilder.MergeFunctions = CodeGenOpts.MergeFunctions;
PMBuilder.PrepareForThinLTO = CodeGenOpts.EmitSummaryIndex;
PMBuilder.PrepareForLTO = CodeGenOpts.PrepareForLTO;
PMBuilder.RerollLoops = CodeGenOpts.RerollLoops;

然后根据各种LangOpts和CodeGenOpts使用PassManagerBuilder的addExtension接口。

if (CodeGenOpts.DebugInfoForProfiling ||
!CodeGenOpts.SampleProfileFile.empty())
PMBuilder.addExtension(PassManagerBuilder::EP_EarlyAsPossible,
addAddDiscriminatorsPass);

...

if (LangOpts.Sanitize.has(SanitizerKind::LocalBounds)) {
PMBuilder.addExtension(PassManagerBuilder::EP_ScalarOptimizerLate,
addBoundsCheckingPass);
PMBuilder.addExtension(PassManagerBuilder::EP_EnabledOnOptLevel0,
addBoundsCheckingPass);
}

...

if (LangOpts.Sanitize.hasOneOf(SanitizerKind::Efficiency)) {
PMBuilder.addExtension(PassManagerBuilder::EP_OptimizerLast,
addEfficiencySanitizerPass);
PMBuilder.addExtension(PassManagerBuilder::EP_EnabledOnOptLevel0,
addEfficiencySanitizerPass);
}
...

addExtension接受的第一个参数是ExtensionPointTy类型,指定Pass插入的位置。ExtensionPointTy是一个enum,内容如下

enum ExtensionPointTy {
//插入时机尽可能的早,允许在代码从前端出来后就被处理
EP_EarlyAsPossible,

//在模块级优化前
EP_ModuleOptimizerEarly,

//在循环优化后
EP_LoopOptimizerEnd,

//允许插入优化pass在大多数主优化后,在clean-ish优化前
EP_ScalarOptimizerLate,

//在最后
EP_OptimizerLast,

//在vectorizer和其他更高级的平台指定优化之前
EP_VectorizerStart,

//O0下位于inlining pass之后
EP_EnabledOnOptLevel0,

//执行类似于与指令组合的窥孔优化(peephole optimizations),在指令组合pass的每个实例之后
EP_Peephole,

//后期循环的规范化和简化这是最后一个循环优化管道,在循环删除前。
//必须是LoopPass的实例,加入到可以删除循环的地方,例如指定平台的循环方言识别
EP_LateLoopOptimizations,

//通过CGPassManager增加CallGraphSCC pass在主CallGraphSCC pass之后,function simplification passe之前
EP_CGSCCOptimizerLate,
};

后一个参数为ExtensionFn

typedef std::function<void(const PassManagerBuilder &Builder,
legacy::PassManagerBase &PM)>
ExtensionFn;

这个函数里主要调用了PassManager的add接口,将指定的Pass加入队列

PassManagerBuilder中有一个存放Pass添加函数的vector

std::vector<std::pair<ExtensionPointTy, ExtensionFn>> Extensions;

addExtension就是将由ExtensionPointTy和ExtensionFn组成的pair添加到vector中

Pass添加的最后一步

在CreatePasses的最后,调用populate,完成从Extensions到PassManager的添加过程。

PMBuilder.populateFunctionPassManager(FPM);
PMBuilder.populateModulePassManager(MPM);

以populateModulePassManager为例

void PassManagerBuilder::populateModulePassManager(
legacy::PassManagerBase &MPM) {

...

// If all optimizations are disabled, just run the always-inline pass and,
// if enabled, the function merging pass.
if (OptLevel == 0) {
addPGOInstrPasses(MPM);
if (Inliner) {
MPM.add(Inliner);
Inliner = nullptr;
}

// FIXME: The BarrierNoopPass is a HACK! The inliner pass above implicitly
// creates a CGSCC pass manager, but we don't want to add extensions into
// that pass manager. To prevent this we insert a no-op module pass to reset
// the pass manager to get the same behavior as EP_OptimizerLast in non-O0
// builds. The function merging pass is
if (MergeFunctions)
MPM.add(createMergeFunctionsPass());
else if (GlobalExtensionsNotEmpty() || !Extensions.empty())
MPM.add(createBarrierNoopPass());

addExtensionsToPM(EP_EnabledOnOptLevel0, MPM);

// Rename anon globals to be able to export them in the summary.
// This has to be done after we add the extensions to the pass manager
// as there could be passes (e.g. Adddress sanitizer) which introduce
// new unnamed globals.
if (PrepareForThinLTO)
MPM.add(createNameAnonGlobalPass());

return;
}
...
...

从Extension这个命令可以看出来,CreatePass更多的是为了将由CodeGenOpts和LangOpts参数指定的内容分类,而populate才是Pass最终进入执行队列的位置。接下来的代码也大致相同,根据OptLevel、LibraryInfo等信息直接调用add函数添加。

addExtensionsToPM就是将Extensions中的内容添加到PassManager的接口

void PassManagerBuilder::addExtensionsToPM(ExtensionPointTy ETy,
legacy::PassManagerBase &PM) const {
if (GlobalExtensionsNotEmpty()) {
for (auto &Ext : *GlobalExtensions) {
if (Ext.first == ETy)
Ext.second(*this, PM);
}
}
for (unsigned i = 0, e = Extensions.size(); i != e; ++i)
if (Extensions[i].first == ETy)
Extensions[i].second(*this, PM);
}

Pass的添加大致如此。