在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源代码树的所有修改:
- 树(分支’extra-protein’):https://github.com/mshockwave/llvm/tree/extra-protein
- Clang树(也在分支’extra-protein’):https://github.com/mshockwave/clang/tree/extra-protein
LLVM Pass
我不会在这里详细介绍Pass。Pass将被放入lib/Transforms/Scalar/ExtraProtein.cpp
和include/llvm/Transforms/Scalar/ExtraProtein.h
。我们将使用createExtraProteinLegacyPass(uint32_t, uint32_t)
头文件中的factory函数稍后构造一个新的Pass实例。
Clang 内部
在进入真正的编译过程之前,典型的编译器需要大量的前期工作(即词法分析器,解析器……)。例如,查找默认/系统标头路径。现代的“编译器”,例如gcc和clang,经常将这种琐碎的任务卸载到另一个分离的实例中,称为编译器驱动程序,或简称为驱动程序。所以你运行的可执行文件clang实际上是一个驱动程序,它会在设置完所有需求后调用“真正的编译器”。
在文件夹中lib/Driver/ToolChains
(相对于clang的项目根目录),我们可以看到各种编译器驱动程序。例如,从开发商Fuchsia OS创建自己的驱动程序Fuchsia.cpp
和Fuchsia.h
,它可以正确设置Fuchsia OS标题路径和设置默认标志等等。严格地说,该文件夹中的文件不仅仅是驱动程序,而是工具链,它们还描述了编译管道中的其他部分,比如它将使用的汇编器和链接器。
“真正的编译器”的开头是前端,这是我们从教科书中学到的:lexer和parser。在clang中,前端也称为cc1。有时你在网上发现的一些神奇的解决方案告诉你运行如下命令:
clang -cc1 -fsome_flag -some_option ... |
这相当于将标志或选项直接传递给前端。
您还可以通过添加-v选项查看驱动程序传递给前端的选项:
$ clang++ -v -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]>, |
第一行的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=">, |
步骤3.连接驱动程序和前端
现在我们要将驱动程序的命令行选项转换为前端的选项。我们要修改“clang”驱动程序。在档案中lib/Driver/ToolChains/Clang.cpp
。我们将以下行添加到Clang::ConstructJob
方法中:
if(const Arg *A = Args.getLastArg(options::OPT_extra_protein_EQ)) { |
基本上我们什么都不做,只能将“磅”换成“克”。然后在第15行到第17行中,我们使用前端的命令行选项来传递我们的信息。
步骤4.添加新的CodeGen选项
我们终于到了最后阶段:CodeGen。虽然clang中的CodeGen不是单个实例或可执行文件,但它有自己的选项集,它放在CodeGenOptions
类中include/clang/Frontend/CodeGenOptions.h
。我们将为它添加一个简单的成员字段:
struct ProteinAmount { |
Duplicate
字段存储2x
,3x
种蛋白质的量,并修正字段存储那些使用“克”作为蛋白质单元。
接下来,我们ExtraProteinAmount
将使用从前端传递的命令行选项配置CodeGen选项。我们将修改该ParseCodeGenArgs
函数,该函数用于填充大部分CodeGen选项lib/Frontend/CompilerInvocation.cpp
。将以下代码放在函数中的任何位置。
for(const auto& Arg : Args.getAllArgValues(OPT_extra_protein_amount)) { |
这是我们最终将蛋白质量的文本表示转换为记忆内值的地方。
第5步 添加LLVM Pass
在最后一步,我们将把我们添加ExtraProteinPass
到由clang CodeGen运行的Pass管道。我们将触及的东西就是lib/CodeGen/BackendUtil.cpp
。EmitAssemblyHelper::CreatePasses
方法,顾名思义,创建将在CodeGen之后运行的LLVM Pass。我们将在此添加我们的代码ExtraProteinPass
。
if(!CodeGenOpts.ExtraProteinAmount.empty()) { |
代码本身非常简单,这次我们在EmitAssemblyHelper::CreatePasses
最后一行添加代码,因为我们需要两个优化Passes来运行:SROA和Mem2Reg。这两个可以为我们提供更简洁的代码形状供我们ExtraProteinPass
处理。
但是,如果没有给出额外的优化标志,则clang在优化级别零(即-O0)中运行。并且SROA和Mem2Reg不会添加到Pass管道中。如果我们只想使用一个clang命令行选项来启用我们的功能,我们需要将SROA和Mem2Reg添加到Pass管道中,即使在-O0
:
if(!CodeGenOpts.ExtraProteinAmount.empty()) { |
另外,在-O0
,clang会为所有函数添加一个 optnone属性。该属性将阻止任何优化Passes在附加函数上运行。因此,如果有任何“额外的蛋白质”,我们还需要告诉clang不要添加这个属性。我们要调用的函数是CodeGenModule::SetLLVMFunctionAttributesForDefinition
在lib/CodeGen/CodeGenModule.cpp
。通过添加新的guard语句来修改与ShouldAddOptNone
变量相关的行,用于控制optnone
生成过程:
... |
本教程提供了一种有趣而又彻底的方式来查看Clang的内部结构。正如您所看到的,本文中的代码并不难,大多数都是自我解释的。