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

简介: 本书的前半部分将向您介绍怎么样去配置、构建、和安装LLVM的不同软件库、工具和外部项目。接下来,本书的后半部分将向您介绍LLVM的各种设计细节,并逐步地讲解LLVM的各个编译步骤:前段、中间表示(IR)、后端、即时编译(JIT)引擎、跨平台编译和插件接口。本书包含有大量翔实的示例和代码片段,以帮助读者平稳顺利的掌握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源代码树中,如下所示:

image.png

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

image.png

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

image.png

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

image.png

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文件夹内,如以下命令序列所示:

image.png

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

image.png

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

image.png
image.png

2.1.3 实验Compiler-RT

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

image.png
image.png

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

image.png

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

image.png

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

image.png

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是这种新场景的编译流程。

image.png

如果你愿意,可以使用-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,请使用以下命令:

image.png

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

image.png

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

image.png

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

image.png

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

image.png

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

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

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

image.png

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

image.png

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

image.png

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

image.png

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

image.png

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

image.png

请记住,出于性能方面的原因,除了目标文件之外,真正的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的资源。
要获取源代码,请使用以下命令:

image.png

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

image.png

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

image.png

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

image.png

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

image.png

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文件夹中,如下例所示:

image.png

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

image.png

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

image.png

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

image.png

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

image.png

使用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:

image.png

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

image.png

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

image.png

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

image.png

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

image.png

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++的替代品。

image.png

据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++,首先需要下载源代码包:

image.png

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

image.png

还可以使用GIT镜像:

image.png

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

image.png

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

image.png

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

image.png

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

image.png

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

image.png

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

image.png

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

2.3 总结

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

相关实践学习
阿里云图数据库GDB入门与应用
图数据库(Graph Database,简称GDB)是一种支持Property Graph图模型、用于处理高度连接数据查询与存储的实时、可靠的在线数据库服务。它支持Apache TinkerPop Gremlin查询语言,可以帮您快速构建基于高度连接的数据集的应用程序。GDB非常适合社交网络、欺诈检测、推荐引擎、实时图谱、网络/IT运营这类高度互连数据集的场景。 GDB由阿里云自主研发,具备如下优势: 标准图查询语言:支持属性图,高度兼容Gremlin图查询语言。 高度优化的自研引擎:高度优化的自研图计算层和存储层,云盘多副本保障数据超高可靠,支持ACID事务。 服务高可用:支持高可用实例,节点故障迅速转移,保障业务连续性。 易运维:提供备份恢复、自动升级、监控告警、故障切换等丰富的运维功能,大幅降低运维成本。 产品主页:https://www.aliyun.com/product/gdb
相关文章
|
2月前
|
存储 自然语言处理 编译器
【C语言】编译与链接:深入理解程序构建过程
【C语言】编译与链接:深入理解程序构建过程
|
3月前
|
存储 C语言
C语言程序设计核心详解 第七章 函数和预编译命令
本章介绍C语言中的函数定义与使用,以及预编译命令。主要内容包括函数的定义格式、调用方式和示例分析。C程序结构分为`main()`单框架或多子函数框架。函数不能嵌套定义但可互相调用。变量具有类型、作用范围和存储类别三种属性,其中作用范围分为局部和全局。预编译命令包括文件包含和宏定义,宏定义分为无参和带参两种形式。此外,还介绍了变量的存储类别及其特点。通过实例详细解析了函数调用过程及宏定义的应用。
|
4月前
|
前端开发 JavaScript Java
hyengine 编译问题之复用脚本引擎如何解决
hyengine 编译问题之复用脚本引擎如何解决
|
6月前
|
存储 Java 编译器
心得经验总结:源代码、目标代码、可执行代码、本地代码的区别
心得经验总结:源代码、目标代码、可执行代码、本地代码的区别
200 0
|
IDE Unix 编译器
关于编译的重要概念总结
关于编译的重要概念总结
3392 0
关于编译的重要概念总结
|
Python
python代码编译发布为可执行文件【保姆级别操作指导,建议收藏】
今天群里有朋友问到如何将python代码编译为exe文件,刚好有时间,现写了。
447 0
python代码编译发布为可执行文件【保姆级别操作指导,建议收藏】
|
编译器 C++ 前端开发
带你读《LLVM编译器实战教程》之三:工具和设计
本书的前半部分将向您介绍怎么样去配置、构建、和安装LLVM的不同软件库、工具和外部项目。接下来,本书的后半部分将向您介绍LLVM的各种设计细节,并逐步地讲解LLVM的各个编译步骤:前段、中间表示(IR)、后端、即时编译(JIT)引擎、跨平台编译和插件接口。本书包含有大量翔实的示例和代码片段,以帮助读者平稳顺利的掌握LLVM的编译器开发环境。
|
编译器 iOS开发 Windows
带你读《LLVM编译器实战教程》之一:构建和安装LLVM
本书的前半部分将向您介绍怎么样去配置、构建、和安装LLVM的不同软件库、工具和外部项目。接下来,本书的后半部分将向您介绍LLVM的各种设计细节,并逐步地讲解LLVM的各个编译步骤:前段、中间表示(IR)、后端、即时编译(JIT)引擎、跨平台编译和插件接口。本书包含有大量翔实的示例和代码片段,以帮助读者平稳顺利的掌握LLVM的编译器开发环境。
20229 0