带你读《LLVM编译器实战教程》之二:外部项目

点击查看第一章
点击查看第三章

第2章 外 部 项 目

不包含于核心LLVM和Clang存储库的项目需要单独下载。在本章中,我们将介绍各种其他官方LLVM项目,并介绍如何构建和安装它们。仅对核心LLVM工具感兴趣的读者可以跳过本章,或在需要时再翻阅。
在本章中,我们将介绍以下项目及其安装方法:

  • Clang 外部工具
  • Compiler-RT
  • DragonEgg
  • LLVM测试套件
  • LLDB
  • libc++

除了本章所涉及的项目之外,还有两个在本书范围之外的官方LLVM项目:Polly(多面体优化器)以及lld(目前正在开发的LLVM链接器)。
预构建的二进制包不包含本章中提及的任何外部项目(Compiler-RT除外)。因此,与上一章不同,我们将仅介绍如何下载源代码并自行构建它们。
读者不要指望本章介绍的项目与核心LLVM/Clang项目的成熟度相同,因为其中一些项目只是实验性的,或处于起步阶段。

2.1 Clang外部项目介绍

LLVM中最引人注目的设计就是将后端与前端隔离为两个独立的项目,即LLVM核心和Clang。LLVM开始时是以LLVM中间表示(IR)为中心的一组工具,并且依赖于可自行修改的GCC将高级语言程序转换为独有的IR形式,并存储在位码(bitcode)文件中。位码是一个术语,它模仿了Java字节码的命名。Clang作为LLVM团队专门设计的第一个前端,是LLVM项目的一个重要里程碑,它有着与LLVM核心相同的代码质量、清晰的文档和库组织结构。它不仅可以将C和C++程序转换为LLVM IR,还可以作为灵活的编译器驱动程序对整个编译过程进行监督,以便尽可能保持与GCC的兼容性。
我们后面会称Clang为前端程序,而不是驱动程序,它负责将C和C++程序转换为LLVM IR。Clang库的一大亮点是可以用于编写强大的工具,比如C++代码重构工具和源代码分析工具,从而使C++程序员可以*地研究C++的热点问题。Clang预包装的一些工具可以帮助你了解如何利用这些库,比如:

  • Clang Check:它能够执行语法检查,还能应用快速修复以解决常见问题,还可以转储任何程序的内部Clang抽象语法树(AST)表示。
  • Clang Format:它包含一个工具和一个LibFormat库,它们不仅可以缩进代码,还可以将任何一部分C++代码格式化为任何样式,以符合LLVM编码标准以及Google、Chromium、Mozilla或者WebKit的样式指南。

clang-tools-extra存储库是建立在Clang之上的多个应用程序的集合,它们能够读取大型C或C++代码库,并执行各种代码重构和分析。我们在下面列出这个包中的一些工具,但不是全部:

  • Clang Modernizer:它是一个代码重构工具,用于扫描C++代码并更改旧样式的结构,以符合较新标准(例如C++ 11标准)提出的更现代的样式。
  • Clang Tidy:它是一个错误检查工具,用于检查违反LLVM或Google编码标准的常见编程错误。
  • Modularize:它可以帮助你识别适合组成模块的C++头文件,“模块”是C++标准化委员会正在讨论的新概念(有关更多信息,请参阅第10章)。
  • PPTrace:它是一个简单工具,用于跟踪Clang C++预处理器的活动。

有关如何使用这些工具以及如何构建自己的工具的更多信息,请参见第10章。

2.1.1 构建和安装Clang外部工具

可以从http://llvm.org/releases/3.4/clang-tools-extra-3.4.src.tar.gz 获取该项目的3.4版本的官方快照。如果想浏览所有可用的版本,请访问http://llvm.org/releases/download.html 。如果想依靠LLVM构建系统轻松编译这组工具,可以与核心LLVM和Clang的源代码一起构建。为此,必须将源代码目录放入Clang源代码树中,如下所示:

带你读《LLVM编译器实战教程》之二:外部项目

还可以直接从官方的LLVM SVN存储库获取资源:

带你读《LLVM编译器实战教程》之二:外部项目

从上一章得知,如果要获取版本3.4的稳定源代码,可以用tags/RELEASE_34/final替换trunk。或者,如果你喜欢使用GIT版本控制软件,可以使用以下命令行下载它:

带你读《LLVM编译器实战教程》之二:外部项目

将源代码放入Clang树后,必须参照第1章中的编译操作说明,使用CMake或自动工具生成的配置脚本继续操作。要测试安装是否成功,请运行clang-modernize工具,如下所示:

带你读《LLVM编译器实战教程》之二:外部项目

2.1.2 理解Compiler-RT

Compiler-RT(RT指运行时)项目用于为硬件不支持的低级功能提供特定于目标的支持。例如,32位目标通常缺少支持64位除法的指令。Compiler-RT通过提供特定于目标并经过优化的功能来解决这个问题,该功能在使用32位指令的同时实现了64位除法。它提供相同的功能,因此是LLVM项目中libgcc的替代品。此外,它还具有对地址和内存清洗工具的运行时支持。你可以从http://llvm.org/releases/3.4/compiler-rt-3.4.src.tar.gz 下载3.4版本的Compiler-RT,或者在http://llvm.org/releases/download.html 上查找更多版本。
它在基于LLVM的编译工具链中是一个关键组件,因此上一章已经介绍了如何安装Compiler-RT。如果你仍然没有这个组件,请记住将其源代码放入LLVM源代码树中的projects文件夹内,如以下命令序列所示:

带你读《LLVM编译器实战教程》之二:外部项目

如果你愿意,也可以使用它的SVN存储库:

带你读《LLVM编译器实战教程》之二:外部项目

除了SVN存储库,还可以通过GIT镜像下载:

带你读《LLVM编译器实战教程》之二:外部项目
带你读《LLVM编译器实战教程》之二:外部项目

2.1.3 实验Compiler-RT

要查看编译器运行时库启动时的典型情况,可以编写一个执行64位除法的C程序来做一个简单的实验:

带你读《LLVM编译器实战教程》之二:外部项目
带你读《LLVM编译器实战教程》之二:外部项目

如果你有64位x86系统,请使用你的LLVM编译器来实验以下两个命令:

带你读《LLVM编译器实战教程》之二:外部项目

-m32标志指示编译器生成32位x86程序,而-S标志将用于在test-32bit.S中为此程序生成x86汇编语言文件。如果查看这个文件,就会看到每当程序需要执行除法时都会有一个有趣的调用:

带你读《LLVM编译器实战教程》之二:外部项目

该函数由Compiler-RT定义,并演示了将在何处使用该库。但是,如果省略-m32标志并使用64位x86编译器,即与生成test-64bit.S汇编文件的第二个编译器命令一样,则不会再看到需要Compiler- RT协助的程序,因为它可以通过单个指令完成除法运算:

带你读《LLVM编译器实战教程》之二:外部项目

2.2 使用DragonEgg插件

如前所述,LLVM项目初期依赖于GCC,没有自己的C/C++前端。在那时,你需要下载一个名为llvm-gcc的GCC源代码树并将其完整编译,才能使用LLVM。由于编译涉及完整的GCC软件包,需要知道自己重建GCC所需的所有必要的GNU知识,因此这是一项非常耗时且棘手的任务。DragonEgg项目为利用GCC插件系统提出了一个聪明的解决方案,它将LLVM逻辑分离到它自己的一个更小的代码树中。这样,用户不再需要重建整个GCC包,而只需构建一个插件,然后将其加载到GCC中即可。DragonEgg也是LLVM项目下唯一获得GPL授权的项目。
即使Clang已经兴起,DragonEgg至今仍然存在,因为Clang只处理C和C++语言,而GCC能够解析更多种类的语言。通过使用DragonEgg插件,可以使用GCC作为LLVM编译器的前端,从而能够编译GCC支持的大多数语言,包括Ada、C、C++和FORTRAN,并且部分支持Go、Java、Objective-C和Objective-C++。
该插件用LLVM的相应部分替代GCC的中间和后端,并自动执行所有编译步骤,能满足你对一流的编译器驱动程序的期望。图2-1是这种新场景的编译流程。

带你读《LLVM编译器实战教程》之二:外部项目

如果你愿意,可以使用-fplugin-arg-dragonegg-emit-ir -S标志将编译过程停止在LLVM IR生成阶段,并使用LLVM工具分析和调查前端的结果,或者手动使用 LLVM工具完成编译过程。我们将很快看到一个例子。
由于DragonEgg是一个LLVM分支项目,维护人员无法以维护LLVM主项目那样的频率更新该项目。在编写本书时,DragonEgg最新的稳定版本是3.3版本,并且被绑定到LLVM 3.3的工具集中。因此,如果你生成LLVM位码(使用LLVM IR写在磁盘上的程序),则不能使用3.3以外版本的LLVM工具分析此文件,也不能进行优化或继续编译。你可以在http://dragonegg.llvm.org 上找到DragonEgg官方网站。

2.2.1 构建DragonEgg

要编译并安装DragonEgg,请首先从http://llvm.org/releases/3.3/dragonegg-3.3.src.tar.gz 获取源代码。对于Ubuntu,请使用以下命令:

带你读《LLVM编译器实战教程》之二:外部项目

如果你希望从SVN库中获取最新但不稳定的源代码,请使用以下命令:

带你读《LLVM编译器实战教程》之二:外部项目

对于GIT镜像,请使用以下命令:

带你读《LLVM编译器实战教程》之二:外部项目

要执行编译和安装,需要提供LLVM安装路径。LLVM版本必须与正在安装的DragonEgg版本相匹配。假设使用与第1章中相同的安装前缀/usr/local/llvm,并假设GCC 4.6已安装并存在于你的shell PATH变量中,则应使用以下命令:

带你读《LLVM编译器实战教程》之二:外部项目

请注意,该项目缺少自动工具或CMake项目文件。你应该使用make命令直接构建。如果你的gcc命令已经提供了你需要的正确GCC版本,则可以在运行make时省略GCC = gcc-4.6前缀。构建后将生成名为dragonegg.so并且格式为共享库的插件,你可以使用以下GCC命令行调用该插件(假设你正在编译一个经典的“Hello,World!” C代码):

带你读《LLVM编译器实战教程》之二:外部项目

虽然DragonEgg理论上支持GCC 4.5及更高版本,但强烈建议使用GCC 4.6。DragonEgg没有在其他GCC版本中进行过广泛测试和维护。

2.2.2 使用DragonEgg和LLVM工具了解编译流程

如果你希望看到前端的运行情况,请使用-S -fplugin-arg-dragonegg-emit-ir标志,该标志将产生以LLVM IR代码表示的人工可读文件:

带你读《LLVM编译器实战教程》之二:外部项目

一旦编译器将程序转换为IR则停止编译,并将内存中的表示内容写入磁盘的能力是LLVM的一个独有特征。大多数其他编译器无法做到这一点。在欣赏LLVM IR如何表示源程序之后,你可以手动使用多个LLVM工具继续完成编译过程。以下命令调用一个特殊的汇编程序,将LLVM从文本形式转换为二进制形式,仍保存在磁盘上:

带你读《LLVM编译器实战教程》之二:外部项目

如果你愿意,可以用一个特殊的IR反汇编器(llvm-dis)把它翻译回可读的形式。以下工具将在显示成功完成代码转换的相关统计信息的同时,进行独立于编译目标的优化:

带你读《LLVM编译器实战教程》之二:外部项目

-stats标志是可选的。之后,你可以使用LLVM后端工具将其转换为目标机器的汇编语言:

带你读《LLVM编译器实战教程》之二:外部项目

再强调一下,-stats标志是可选的。由于hello.s是一个汇编文件,因此既可以使用GNU binutils汇编器,也可以使用LLVM汇编器。在下面的命令中,我们将使用LLVM汇编器:

带你读《LLVM编译器实战教程》之二:外部项目

因为LLVM链接器项目lld目前正在开发中,还没有集成到核心LLVM项目中,所以LLVM默认使用你的系统链接器。因此,如果你没有lld,可以使用常规的编译器驱动程序来完成编译,这会激活你的系统链接器:

带你读《LLVM编译器实战教程》之二:外部项目

请记住,出于性能方面的原因,除了目标文件之外,真正的LLVM编译器驱动程序在任何阶段都不会将程序表示内容写入磁盘,因为它仍然缺少集成的链接器。它会使用内存中的表示内容并协调几个LLVM组件进行编译。

2.2.3 理解LLVM测试套件

LLVM测试套件包括一套用于测试LLVM编译器的官方基准程序。该测试套件对于LLVM开发人员非常有用,它通过编译和运行这些程序来验证优化和编译器的改进。如果正在使用LLVM的非稳定版本,或者更改了LLVM源代码并怀疑某些功能不能正常工作,那么可以自行运行该测试套件。但请记住,在LLVM主源代码树中存在更简单的LLVM回归测试和单元测试,可以使用make check-all轻松运行它们。测试套件不同于传统的回归测试和单元测试,因为它包含了整个基准程序。
必须将LLVM测试套件放在LLVM源代码树中,以允许LLVM构建系统识别它。可以从http://llvm.org/releases/3.4/test-suite-3.4.src.tar.gz 找到版本3.4的资源。
要获取源代码,请使用以下命令:

带你读《LLVM编译器实战教程》之二:外部项目

如果你喜欢通过SVN下载,以获得最新但可能不稳定的版本,请使用以下命令:

带你读《LLVM编译器实战教程》之二:外部项目

如果你喜欢使用GIT,请使用以下命令:

带你读《LLVM编译器实战教程》之二:外部项目

需要重新生成LLVM的构建文件才能使用测试套件。在此特例中,不能使用CMake。必须使用经典的配置脚本来构建测试套件。读者可以参考第1章中介绍的配置步骤。
测试套件有一套Makefile文件,用于测试和检查基准。也可以提供一个自定义的Makefile来评估自定义程序。请将自定义Makefile文件放在测试套件的源代码目录中,并使用命名模板llvm/projects/test-suite/TEST..Makefile命名该文件,
其中,必须将记号替换为所需的任何名称,比如llvm/projects/test-suite/TEST.example.Makefile。

带你读《LLVM编译器实战教程》之二:外部项目

在配置期间,将在基准测试程序将要运行的LLVM对象目录中创建测试套件的目录。要运行并测试示例Makefile文件,请进入第1章中的对象目录路径,然后执行以下命令:

带你读《LLVM编译器实战教程》之二:外部项目

2.2.4 使用LLDB

LLDB(低级调试器)项目是一个用LLVM基础架构构建的调试器,它作为在Mac OS X上的Xcode 5调试器而被积极开发出来。从2011年开始开发到写本书时为止,LLDB还没有在Xcode范围之外发布一个稳定的版本。可以从http://llvm.org/releases/3.4/lldb-3.4.src.tar.gz 获取LLDB资源。像许多依赖于LLVM的项目一样,可以通过将其集成到LLVM构建系统中来轻松构建它。要做到这一点,只需将其源代码放在LLVM tools文件夹中,如下例所示:

带你读《LLVM编译器实战教程》之二:外部项目

也可以使用其SVN存储库来获得最新版本:

带你读《LLVM编译器实战教程》之二:外部项目

如果你愿意,还可以使用GIT镜像来获取它:

带你读《LLVM编译器实战教程》之二:外部项目

对于GNU/Linux系统,LLDB仍然处于实验阶段。
在构建之前,请注意LLDB有一些软件先决条件:Swig、libedit(仅适用于Linux)和Python。例如,在Ubuntu系统上,可以使用以下命令来解决这些依赖关系:

带你读《LLVM编译器实战教程》之二:外部项目

请记住,与本章介绍的其他项目一样,需要重新生成LLVM构建文件以允许进行LLDB编译。请按照第1章中所述的步骤从源代码构建LLVM。
要对最近的lldb安装进行简单测试,只需使用-v标志运行,即可打印其版本:

带你读《LLVM编译器实战教程》之二:外部项目

使用LLDB执行调试会话
为了演示如何使用LLDB,我们将启动一个调试会话来分析Clang二进制文件。你可以看到Clang二进制文件包含许多的C++符号。如果你使用默认选项编译LLVM/Clang项目,将得到带调试符号的Clang二进制文件。如果在运行配置脚本生成LLVM Makefile时省略--enable-optimized标志,或者在运行CMake文件时使用-DCMAKE_BUILD_TYPE="Debug"(这是默认构建类型),都会发生这种情况。
如果你熟悉GDB,可能有兴趣参考http://lldb.llvm.org/lldb-gdb.html中的表格,该表格列出了常用GDB命令及相应的对等LLDB命令。
与GDB一样,我们通过传递将要调试的可执行文件的路径作为命令行参数来启动LLDB:

带你读《LLVM编译器实战教程》之二:外部项目

为了开始调试,我们将命令行参数提供给Clang二进制文件。我们将使用-v参数,它将打印Clang版本:

带你读《LLVM编译器实战教程》之二:外部项目

在LLDB到达我们的断点之后,就可以用next命令单步执行每一行C++代码。与GDB一样,LLDB也可以接受任何命令缩写,前提是这个缩写不会带来歧义,例如用n代替next:

带你读《LLVM编译器实战教程》之二:外部项目

要查看LLDB如何打印C++对象,请在声明argv或ArgAllocator对象之后单步执行到达该行并打印它:

带你读《LLVM编译器实战教程》之二:外部项目

你觉得满意后,用q命令退出调试器:

带你读《LLVM编译器实战教程》之二:外部项目

2.2.5 libc++标准库介绍

libc++库是LLVM项目重写的C++标准库,它支持最新的C++标准(包括C++ 11和C++ 1y),并且在MIT许可证和UIUC许可证下获得双重许可。libc++库是Compiler-RT的重要伙伴,是用Clang++构建最终C++可执行文件时所使用的运行时库的一部分,必要时也包含libclc(OpenCL运行时库)。它不同于Compiler-RT,因为并非一定要构建它。Clang并不局限于libc++,在没有libc++的情况下,它可以将你的程序与GNU libstdc++链接。如果这两个库都存在,你可以使用-stdlib选项指定Clang++使用哪个库。libc++库支持x86和x86_64处理器,而且它被设计为用于Mac OS X和GNU/Linux系统的GNU libstdc++的替代品。

带你读《LLVM编译器实战教程》之二:外部项目

据libc++开发人员称,继续开发GNU libstdc++的主要障碍之一是需要重写代码来支持更新的C++标准,并且主线libstdc++开发切换到GPLv3许可证之后,以至于一些依赖于LLVM项目的公司无法使用。请注意,LLVM项目在商业产品中经常使用与GPL许可不兼容的方式。面对这些挑战,LLVM社区决定主要为Mac OS X开发新的C++标准库,同时支持Linux。
在你的苹果电脑中获取libc++的最简单方法是安装Xcode 4.2或更高版本。
如果你打算自己为GNU/Linux机器构建库,请记住,C++标准库由库本身和一个低级函数层组成,这个函数层实现了用于处理异常和运行时类型信息(RTTI)的若干功能。这种关注点的分离使得C++标准库更容易移植到其他系统。在构建标准库时,它也提供了不同的选项。你可以构建与libsupc++(这个较低层的GNU实现)或者libc++ abi(LLVM团队的实现)链接的libc++。不过,libc++ abi目前只支持Mac OS X系统。
要在GNU/Linux机器上用libsupc++构建libc++,首先需要下载源代码包:

带你读《LLVM编译器实战教程》之二:外部项目

在撰写本书之前,仍然无法像在其他项目中那样,依靠LLVM构建系统来创建库文件。因此,请注意,这次我们没有将libc++源代码放入LLVM源代码树中。
另外,也可以使用SVN版本库中的实验性版本:

带你读《LLVM编译器实战教程》之二:外部项目

还可以使用GIT镜像:

带你读《LLVM编译器实战教程》之二:外部项目

只要你有基于LLVM的工作编译器,就需要生成只使用基于LLVM的新编译器的libc++构建文件。在这个例子中,我们假定我们的路径中有一个LLVM 3.4工作编译器。
要使用libsupc++,首先需要知道它的头文件在你的系统中安装在什么位置。由于它是GNU/Linux的常规GCC编译器的一部分,因此可以使用以下命令来找到它:

带你读《LLVM编译器实战教程》之二:外部项目

通常,前两个路径即libsupc++头文件的位置。要确认这一点,请查找libsupc++头文件(如bits/exception_ptr.h)是否存在:

带你读《LLVM编译器实战教程》之二:外部项目

之后,生成libc++构建文件,以便使用基于LLVM的编译器编译它。要执行此操作,请分别改写用于定义系统C和C++编译器的shell CC和CXX环境变量,以使用你想嵌入libc++的LLVM编译器。要使用CMake与libsupc++一起构建libc++,需要定义CMake参数LIBCXX_CXX_ABI(该参数定义要使用的低级库)和参数LIBCXX_LIBSUPCXX_INCLUDE_PATHS(这是一个用分号分隔的路径列表,路径指向你刚发现的包含libsupc++ include文件的文件夹):

带你读《LLVM编译器实战教程》之二:外部项目

在这个阶段,确保../libcxx是到达libc++源文件夹的正确路径。运行make命令来构建项目。使用sudo作为安装命令,因为我们将在/usr中安装库,以便以后可以使用clang++来查找库:

带你读《LLVM编译器实战教程》之二:外部项目

调用clang++编译C++项目时,可以通过使用-stdlib=libc++标志来实验新库和最新的C++标准。
要验证你的新库,请使用以下命令编译一个简单的C++应用程序:

带你读《LLVM编译器实战教程》之二:外部项目

可以使用readelf命令执行一个简单的实验来分析hello二进制文件,并确认它确实与新的libc++库链接:

带你读《LLVM编译器实战教程》之二:外部项目

前面的代码省略了后面的条目。我们看到正好在第一个ELF动态段条目中有一个特定的请求来加载我们刚刚编译的libc++.so.1共享库,由此可以确认我们的C++二进制文件现在使用新的LLVM的C++标准库。你可以在官方项目网站http://libcxx.llvm.org 上找到更多信息。

2.3 总结

LLVM由几个子项目组成,其中一些对主编译器驱动程序来说不是必需的,但却是有用的工具和库。在本章中,我们展示了如何构建和安装这些组件。后续章节将更加详细地探讨这些工具的细节。我们建议读者以后在需要获取构建和安装操作说明时再重读本章。
在下一章中,我们将向你介绍LLVM核心库的设计和工具。

上一篇:开源社区Open-O技术峰会圆满落幕,Release1.0步入正轨


下一篇:基于docker快速搭建ELK