自己动手写LLVM Pass(四)

 

在stackoverflow上有关LLVM的最常见问题之一是:我写了一个Hello World Pass,如何使用clang来运行它,而不是opt?

最常见的解决方案之一是单独使用(传统)PassManager扩展点和-Xclang -load -Xclang MyPass.so命令行选项。

然而,我想知道:

我可以通过简单地将一个命令行选项传递给clang来运行我的Pass或自定义功能吗?

当然,这需要对LLVM源代码树进行一些更改。但我相信这将是学习clang内部以及它如何与LLVM交互的好方法。所以这里有一个简单但又有趣的教程。让我们开始吧!

目标

我们将启用ExtraProteinPass,通过clang的一个命令行选项-add-extra-protein,它将修改代码中所有循环的行程计数。

该选项有几种变体:

  • -add-extra-protein=2x 所有循环的行程计数加倍。
  • -add-extra-protein=5g 仅为所有循环添加额外的5次迭代。
  • -add-extra-protein=1lb 为所有循环添加额外的454次迭代。因为一磅=453.59克

默认情况下,-add-extra-protein等于-add-extra-protein=2x 。

源代码

可以在此处找到本教程中对LLVM / Clang源代码树的所有修改:

LLVM Pass

我不会在这里详细介绍Pass。Pass将被放入lib/Transforms/Scalar/ExtraProtein.cppinclude/llvm/Transforms/Scalar/ExtraProtein.h 。我们将使用createExtraProteinLegacyPass(uint32_t, uint32_t)头文件中的factory函数稍后构造一个新的Pass实例。

Clang 内部

在进入真正的编译过程之前,典型的编译器需要大量的前期工作(即词法分析器,解析器……)。例如,查找默认/系统标头路径。现代的“编译器”,例如gcc和clang,经常将这种琐碎的任务卸载到另一个分离的实例中,称为编译器驱动程序,或简称为驱动程序。所以你运行的可执行文件clang实际上是一个驱动程序,它会在设置完所有需求后调用“真正的编译器”。

在文件夹中lib/Driver/ToolChains(相对于clang的项目根目录),我们可以看到各种编译器驱动程序。例如,从开发商Fuchsia OS创建自己的驱动程序Fuchsia.cppFuchsia.h,它可以正确设置Fuchsia OS标题路径和设置默认标志等等。严格地说,该文件夹中的文件不仅仅是驱动程序,而是工具链,它们还描述了编译管道中的其他部分,比如它将使用的汇编器和链接器。

“真正的编译器”的开头是前端,这是我们从教科书中学到的:lexer和parser。在clang中,前端也称为cc1。有时你在网上发现的一些神奇的解决方案告诉你运行如下命令:

 

clang -cc1 -fsome_flag -some_option ...
#或者
clang -Xclang -fsome_flag -Xclang -some_option

这相当于将标志或选项直接传递给前端。
您还可以通过添加-v选项查看驱动程序传递给前端的选项:

 

$ clang++ -v -c hello.cc
...
"/path/to/clang" -cc1 -triple x86_64-apple-macosx10.13.0 -Wdeprecated-objc-isa-usage -Werror=deprecated-objc-isa-usage -emit-obj -mrelax-all -disable-free -disable-llvm-verifier -discard-value-names ... -o hello.o -x c++ ./hello.cc

正如您在上面看到的那样,最初我们只提供选项-c hello.cc。但是驱动程序添加了许多其他选项,如-cc1后面所显示的,他们将传递给clang前端。

当clang中的前端最终构造AST(抽象语法树)时,它需要生成相应的LLVM IR代码。此阶段称为CodeGen,可能与LLVM中的CodeGen混淆,后者从LLVM IR生成本机代码。

  • en in clang:AST - > LLVM IR
  • LLVM中的CodeGen:LLVM IR - >本机代码

步骤1.为驱动程序添加新的命令行选项

Clang和LLVM不仅因其生成的代码质量而闻名,而且还因其出色的框架而闻名。在这种情况下,为驱动程序添加新的命令行选项只需要少于五行

驱动程序的常用命令行选项在TableGen文件中定义:include/clang/Driver/Options.td。(如果您不熟悉TableGen,那没关系,因为这里使用的语法非常简单,您可能在几分钟内自己解决)在文件中的某处找到并添加以下行:

 

def extra_protein_EQ : Joined<["-", "--"], "add-extra-protein=">, Flags<[DriverOption]>,
  HelpText<"Add extra protein for all loops.">;

def extra_protein : Flag<["-", "--"], "add-extra-protein">, Flags<[DriverOption]>,
  Alias<extra_protein_EQ>, AliasArgs<["2x"]>,
  HelpText<"Add 2x extra protein for all loops.">;

第一行的extra_protein_EQ是标记变量,冒号之后的列(如Joined<…>Flags<…>HelpText<…> )是用来对Flag的描述。例如Joined<[“-”, “ — “], “add-extra-protein=”>
说明该选型在命令行中使用的格式,下面的部分定义了别名规则。
这样,当您没有给-add-extra-protein传递任何值时,它仍然会为该extra_protein_EQ选项提供默认值。

现在你可以在clang中使用-add-extra-protein选项。但当然没有任何事情会发生。我们稍后将定义其相关操作。在此之前,我们将首先为前端添加新选项。

步骤2.为前端添加新的命令行选项

如前所述,驱动程序负责引导过程,它会将驱动程序选项“扩展”为另一组选项,然后将其传递给前端。由于驱动程序和前端是两个不同的实例,基本上,它们有不同的选项集。

前端的选项也在TableGen文件中定义:include/clang/Driver/CC1Options.td。将以下行添加到此文件的任何位置,(例如 let Group = Action\_Group in{…} )。

 

def extra_protein_amount : Joined<["-"], "extra-protein-amount=">,
  HelpText<"Amount of extra protein want to add for all loops">;

步骤3.连接驱动程序和前端

现在我们要将驱动程序的命令行选项转换为前端的选项。我们要修改“clang”驱动程序。在档案中lib/Driver/ToolChains/Clang.cpp 。我们将以下行添加到Clang::ConstructJob方法中:

 

if(const Arg *A = Args.getLastArg(options::OPT_extra_protein_EQ)) {
  StringRef Val = A->getValue();
  uint32_t NumAmount = 0;
  Val.consumeInteger(10, NumAmount);
  if(Val == "lb") {
    // Turn 'pound' to 'gram'
    NumAmount *= 454; // 1 pound == 453.59 gram
    Val = "g";
  }

  // Create command line options for frontend
  SmallString<8> ProteinAmount;
  ProteinAmount.assign(std::to_string(NumAmount));
  ProteinAmount.append(Val);
  CmdArgs.push_back(
    Args.MakeArgString(Twine("-extra-protein-amount=") + ProteinAmount)
  );
}

基本上我们什么都不做,只能将“磅”换成“克”。然后在第15行到第17行中,我们使用前端的命令行选项来传递我们的信息。

步骤4.添加新的CodeGen选项

我们终于到了最后阶段:CodeGen。虽然clang中的CodeGen不是单个实例或可执行文件,但它有自己的选项集,它放在CodeGenOptions类中include/clang/Frontend/CodeGenOptions.h 。我们将为它添加一个简单的成员字段:

 

struct ProteinAmount {
  uint32_t Duplicate;
  uint32_t Amend;
  ProteinAmount() : Duplicate(0U), Amend(0U) {}
  inline bool empty() const {
    return !Duplicate && !Amend;
  }
};
ProteinAmount ExtraProteinAmount;

Duplicate字段存储2x3x种蛋白质的量,并修正字段存储那些使用“克”作为蛋白质单元。

接下来,我们ExtraProteinAmount将使用从前端传递的命令行选项配置CodeGen选项。我们将修改该ParseCodeGenArgs函数,该函数用于填充大部分CodeGen选项lib/Frontend/CompilerInvocation.cpp 。将以下代码放在函数中的任何位置。

 

 for(const auto& Arg : Args.getAllArgValues(OPT_extra_protein_amount)) {
  StringRef Val(Arg);
  if(Val.endswith("x")) {
    // Duplicate
    uint32_t Num = 0;
    Val.consumeInteger(10, Num);
    Opts.ExtraProteinAmount.Duplicate += Num;
  } else {
    // Amend
    uint32_t Num = 0;
    Val.consumeInteger(10, Num);
    Opts.ExtraProteinAmount.Amend += Num;
  }
}

这是我们最终将蛋白质量的文本表示转换为记忆内值的地方。

第5步 添加LLVM Pass

在最后一步,我们将把我们添加ExtraProteinPass到由clang CodeGen运行的Pass管道。我们将触及的东西就是lib/CodeGen/BackendUtil.cpp 。EmitAssemblyHelper::CreatePasses方法,顾名思义,创建将在CodeGen之后运行的LLVM Pass。我们将在此添加我们的代码ExtraProteinPass

 

if(!CodeGenOpts.ExtraProteinAmount.empty()) {
  MPM.add(createExtraProteinLegacyPass(CodeGenOpts.ExtraProteinAmount.Duplicate,
                                       CodeGenOpts.ExtraProteinAmount.Amend));
}

代码本身非常简单,这次我们在EmitAssemblyHelper::CreatePasses最后一行添加代码,因为我们需要两个优化Passes来运行:SROA和Mem2Reg。这两个可以为我们提供更简洁的代码形状供我们ExtraProteinPass处理。

但是,如果没有给出额外的优化标志,则clang在优化级别零(即-O0)中运行。并且SROA和Mem2Reg不会添加到Pass管道中。如果我们只想使用一个clang命令行选项来启用我们的功能,我们需要将SROA和Mem2Reg添加到Pass管道中,即使在-O0 :

 

 if(!CodeGenOpts.ExtraProteinAmount.empty()) {
  // We need mem2reg and sroa for better code shape
  // these two would be added by default when OptLevel >= 1
  // so make sure they're added even when OptLevel == 0
  if(PMBuilder.OptLevel < 1) {
    FPM.add(createSROAPass());
    MPM.add(createPromoteMemoryToRegisterPass());
  }
  MPM.add(createExtraProteinLegacyPass(CodeGenOpts.ExtraProteinAmount.Duplicate,CodeGenOpts.ExtraProteinAmount.Amend));
}

另外,在-O0,clang会为所有函数添加一个 optnone属性。该属性将阻止任何优化Passes在附加函数上运行。因此,如果有任何“额外的蛋白质”,我们还需要告诉clang不要添加这个属性。我们要调用的函数是CodeGenModule::SetLLVMFunctionAttributesForDefinitionlib/CodeGen/CodeGenModule.cpp。通过添加新的guard语句来修改与ShouldAddOptNone变量相关的行,用于控制optnone生成过程:

 

...
ShouldAddOptNone &= !D->hasAttr<AlwaysInlineAttr>();
ShouldAddOptNone &= CodeGenOpts.ExtraProteinAmount.empty();

本教程提供了一种有趣而又彻底的方式来查看Clang的内部结构。正如您所看到的,本文中的代码并不难,大多数都是自我解释的。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值