Переход LLVM от добавления к выполнению

задняя часть C++ LLVM

лязг переход

Чтобы понять процесс добавления и выполнения Pass, мы должны сначала понять, как перейти от clang к LLVM в предыдущем разделе компиляции.

Когда AST создан, он передается для обработки подклассу BackendConsumer класса ASTConsumer. Во-первых, преобразование из AST в IR будет производиться через CodeGenerator. HandleTranslationUnit вызывается после завершения перевода

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

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

Здесь будут вызываться две функции, EmbedBitcode используется для обработки параметра -fembed-bitcode, цель — добавить секцию для хранения биткода в сгенерированный obj файл.

Определение 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. Добавляйте и управляйте двумя командами PassManager и Passes в CreatePasses.

В дополнение к PerModulePasses и PerFunctionPasses существует также CodeGenPasses, который добавляет проходы для преобразования и генерации инструкций в соответствии с требованиями компиляции CodeGenOpts.

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;
}

Когда пропуск добавлен, начните по одному

{
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.

// 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;

Затем используйте интерфейс addExtension PassManagerBuilder в соответствии с различными LangOpts и CodeGenOpts.

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 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 для добавления указанного прохода в очередь.

В PassManagerBuilder есть вектор, в котором хранится функция добавления Pass

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

addExtension — добавить к вектору пару, состоящую из ExtensionPointTy и ExtensionFn.

Последний шаг добавления пропуска

В конце CreatePasses вызовите populate, чтобы завершить процесс добавления расширений в 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 и другой информацией.

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 примерно такое же.