LLVM:编译器领域的强大基石

LLVM 计划启动于 2000 年,最初由美国 UIUC 大学的 Chris Lattner 博士主持开展。它起源于对底层虚拟机的探索,虽然后来名称的含义有所变化,但 LLVM 已经成为编译器领域的重要项目。
发展历程中,2005 年 Apple 雇了 Chris Lattner,LLVM 也相当于成了 Apple 的官方支持的编译器。此后,LLVM 不断发展,包括一系列子项目的同步发布。如 2011 - 12 - 02 LLVM 3.0 发布,改进了 C++ 程序编译支持,实现支持即将发布的 C1x 标准的某些特性等。2012 年,LLVM 荣获 ACM 软件系统奖,进一步证明了其在编译器领域的重要地位。
目前,LLVM 已经被 Apple、Microsoft、Google、Facebook 等各大公司采用。它以 C++ 编写而成,用于优化以任意程序语言编写的程序的编译时间、链接时间、运行时间以及空闲时间。LLVM 的项目是一个模块化和可重复使用的编译器和工具技术的集合,提供了现代化的编译策略,能够同时支持静态和动态的任意编程语言的编译目标。
在编译器领域,LLVM 有着独特的优势。它的设计高度模块化,使得代码更为清晰,便于排查问题。同时,其语言无关的中间代码一方面能够将不同的语言相互连结起来,另一方面也能与 IDE 紧密交互和集成。此外,LLVM 提供的工具可以比较容易地实现新的编程语言的优化编译器或 VM,为现有编程语言引入更好的优化 / 调试特性。
二、独特特点铸就卓越性能

(一)语言与目标无关性
LLVM 设计为可与多种编程语言一起使用,例如 C、C++、Rust、Swift 等。它可以作为不同语言的后端,将高级语言编译成机器无关的中间表示(IR),然后进行优化和转换。生成的 IR 可以进一步编译成不同目标平台(如 x86、ARM、MIPS 等)的机器代码。这使得 LLVM 成为一个高度可移植的编译器框架。例如,一个用 C++ 编写的程序,可以通过 LLVM 编译成在不同硬件平台上运行的机器代码,无需为每个平台单独编写编译器。
(二)模块化与可重用性
LLVM 采用模块化设计,其各个组件(如编译器前端、优化器和后端)都可以独立使用。这种设计促进了代码的重用和扩展。以编译器前端为例,不同的编程语言可以有各自的前端,但都可以将代码转换为统一的 LLVM IR。然后,中间的优化器可以对 IR 进行各种优化,而不依赖于特定的编程语言或目标平台。后端则负责将优化后的 IR 转换为特定目标平台的机器代码。这样,开发者可以根据需要选择和组合不同的组件,提高开发效率。
(三)强大的优化工具
LLVM 提供了一系列的中间代码优化工具,这些工具可以用于静态分析、性能优化等多种目的。优化过程可以在不同的编译阶段进行,以提高代码的运行效率。例如,常量折叠可以在编译时将常量表达式计算出来,减少运行时的计算量。循环优化可以提高循环的执行效率,减少内存访问次数。此外,LLVM 还提供了一些高级的优化技术,如向量化和并行化,可以充分利用现代处理器的多核架构,提高程序的性能。
(四)支持多种编译方式
LLVM 支持即时编译(JIT)和静态编译。即时编译意味着它可以在运行时将代码编译成机器语言,这样可以根据运行时的情况进行优化,提高程序的性能。静态编译则是在编译时将代码编译成机器语言,生成可执行文件。这种双重支持使得开发者可以根据不同的需求选择合适的编译方式。例如,在开发过程中,可以使用即时编译进行快速调试和测试;在发布产品时,可以使用静态编译生成高效的可执行文件。
(五)丰富的生态系统
LLVM 拥有一个庞大的生态系统,包括各种编程语言的前端(如 Clang for C/C++)、工具链(如 lld 链接器、llvm-objdump 等)和库(如编译器 IR 构建库)。这个生态系统还在不断增长,为开发者提供了丰富的资源和工具。例如,Clang 是一个基于 LLVM 的 C/C++/Objective-C 编译器,它提供了快速编译、有用的错误和警告消息,以及用于构建优秀源代码工具的平台。LLDB 是一个基于 LLVM 的调试器,它使用 Clang AST 和表达式解析器、LLVM JIT、LLVM 反汇编程序等,提供了 “正常工作” 的体验。此外,还有许多其他项目使用 LLVM 的组件来执行各种任务,如编译 Ruby、Python、Haskell、Java 等语言。
三、工作原理揭秘

(一)三段式设计
LLVM 采用了独特的三段式设计,包括前端、优化器和后端。
前端负责对不同编程语言的源代码进行分析,包括词法分析、语法分析和语义分析等过程,最终生成基于语言特性的抽象语法树(AST),并将其转换为 LLVM 的中间表示(IR)。例如,对于 C++ 语言,Clang 作为前端可以将 C++ 源代码转换为 LLVM IR。不同的编程语言都可以有各自的前端,但最终都能生成统一的 LLVM IR,这体现了 LLVM 的语言无关性。
中间的优化器只对中间表示 IR 进行操作,通过一系列的优化 pass 对 IR 进行优化。这些优化可以改善代码的运行时间,例如消除冗余的计算、常量折叠等。优化过程不依赖于特定的编程语言或目标平台,使得优化更加高效和通用。
后端负责将优化后的 IR 解释成对应平台的机器码。后端针对不同的目标架构进行优化,生成符合目标机器运行需要的汇编代码,并通过汇编器和链接器最终生成在目标机器上可执行的二进制程序。例如,对于 ARM 架构,后端会生成适合 ARM 处理器执行的机器码。
(二)编译流程详解
LLVM 的编译流程可以分为多个阶段。
首先是预处理阶段,处理包括宏的替换、头文件的导入等。例如,在 C 语言中,#include指令会在这个阶段被处理,将指定的头文件内容插入到源代码中。同时,宏定义也会在这个阶段被展开,用宏定义的字符串替换对应的宏名。
预处理完成后会进行词法分析。代码会被切成一个个 Token,比如大小括号、等于号、字符串等。在这个阶段,编译器会一个一个字母地读取代码,将代码按照预定的规则合并成一个个的标识 tokens,并移除空白符、注释等。
接着是语法分析,它的任务是验证语法是否正确。在词法分析的基础上将单词序列组合成各类语法短语,如 “程序”、“语句”、“表达式” 等,然后将所有节点组成抽象语法树(AST)。语法分析程序判断源程序在结构上是否正确。
完成上面的步骤后就开始生成中间代码 IR(intermediate representation)。代码生成器(Code Generation)会将语法树自顶向下遍历逐步翻译成 LLVM IR。在这个阶段,还会进行一些优化工作,如在 Xcode 的编译设置里可以设置优化的级别 -O1、-O3、-Os 等。
后端会通过一个一个的 Pass 去优化,每个 Pass 做一些事情,最终生成汇编代码。然后生成目标文件,链接需要的动态库和静态库,生成可执行文件。如果开启了 bitCode,苹果会做进一步的优化,生成.bc 的中间代码。
(三)LLVM 命令介绍
LLVM 有许多常用的命令,每个命令都有特定的功能。
例如,clang -emit-llvm -S multiply.c -o multiply.ll可以将 C 语言文件生成 IR 文件。llvm-as test.ll –o test.bc可以将可读的 LLVM IR 文件转换为比特码格式的文件。llc test.bc –o test.s可以将比特码文件转换为目标汇编文件。llvm-dis test.bc –o test.ll可以将比特码文件转换为可读的 LLVM IR 文件。
opt –S –instcombine testfile.ll –o output1.ll可以执行 IR 上的指令合并优化 pass。opt –S –deadargelim testfile.ll –o output2.ll可以执行无效参数优化 pass。
llvm-link test1.bc test2.bc –o output.bc可以链接两个比特码文件。lli output.bc可以在虚拟机上运行比特码格式的程序。
四、应用场景广泛

(一)静态分析工具
在软件开发中,静态分析工具对于确保代码的安全性和可靠性至关重要。LLVM 在静态分析工具开发中有着广泛的应用。例如,Clang Static Analyzer 就是基于 LLVM 的静态代码分析工具,它能够在代码还没有真正运行起来的时候,利用 Clang 对代码进行静态分析。可以应用快捷键 Shift+Command+B 对项目代码进行分析,也可以针对某个文件进行分析(现有版本貌似不能针对特定文件),或者选择在构建过程中同时进行静态分析(深度或者快速)。
静态分析可以发现很多问题,如 Dead store(对一个局部变量赋值后就再也没用过了)、使用到了未初始化的变量、可能存在内存泄露、逻辑上可能存在问题(比如对空指针解引用)等。在新版本的 LLVM 中,新增了一个可能对 App 健壮性有不小帮助的分析点 —— 在集合 / 容器结构中插空。Clang 在静态分析时会对代码逻辑进行一些假设,分析是否有可能插入空值。此外,在内存管理方面也有一些改进,比如内存分配和释放的一致性考虑,以及释放后可能存在的访问野指针问题。
(二)JIT 编译器
LLVM 在即时编译方面有着广泛的应用,例如用于 JavaScript 引擎等。JIT(Just-In-Time Compiler)是一种动态编译中间代码的方式,根据需要,在程序中编译并执行生成的机器码,能够大幅提升动态语言的执行速度。像 Java 语言、.net 平台、luajit 等,广泛使用 JIT 技术,使得程序达到了非常高的执行效率,逐渐接近原生机器语言代码的性能。
LLVM 的 JIT 引擎工作原理是将原来编译器要生成机器码的部分直接写入到当前的内存中,然后通过函数指针的转换,找到对应的机器码并进行执行。但实践中往往需要处理许多头疼的问题,例如内存的管理,符号的重定向,处理外部符号,相当于要处理编译器后端的诸多复杂的事情,真正要设计一款能用的 JIT 引擎还是非常困难的。
使用 LLVM 的 MCJIT 能开发很多东西,比如提供一款解释器的底层工具,将 LLVM 字节码解释执行。具体能够做的事,例如可以制作一款跨平台的 C++ 插件系统,使用 clang 将 C/C++ 代码一次编译到.bc 字节码,然后在各个平台上解释运行。也可以制作一款云调试系统,联网远程向系统注册方法,获取 C++ 客户端的 debug 信息等等。
(三)硬件模拟和仿真
LLVM 可用于生成硬件描述语言模拟代码,加速硬件设计和验证过程。在芯片验证中,数字仿真器主要是针对数字电路的仿真,设计工程师需要应用硬件描述语言 (HDL) 来设计电路,而验证工程师也需要用抽象层的 HDL 来搭建测试环境。仿真工具需要解释编译这些 HDL 代码,并计算出仿真结果,以便工程师检查结果。
例如,SkyEye 是基于可视化建模的硬件行为级仿真平台,支持用户通过拖拽的方式对硬件进行行为级别的仿真和建模。它采用基于 LLVM 的二进制加速技术,加上各种编译器的轻量级的优化技术,从而生成更高效率的主机代码,仿真运行效率大大提升,且具有极致的稳定性,适用于高性能的异构指令模拟仿真。
(四)数据库查询优化
在数据库查询中,LLVM 也有着重要的应用。作为编译优化技术的代表,基于 LLVM 的 CodeGen 技术,能为每个查询生成定制的机器码替代原本的通用函数,减少实际查询时冗余的条件逻辑判断、虚函数调用并提高数据局域性,从而达到提升查询整体性能的目的,成为数据库性能优化的一项重要技术。
然而,LLVM 并非对所有类型的 SQL 都有收益。因为执行实时编译本身需要耗费一定的时间(简单表达式能做到毫秒级,复杂情况在百毫秒级),对于查询本身耗时较少的场景,加入 LLVM 反而会导致性能劣化。目前,LLVM 在 OLAP/HTAP 分析型业务场景中收益较大,有着广泛应用,而在 OLTP 交易型业务场景中,则相对没有那么广泛。但在特定场景下,如 Prepared query(plan cached)的情况下,LLVM 在 OLTP 中也能带来性能提升。
以 GaussDB 为例,GaussDB 针对向量化引擎(主要用于分析场景)、行存(主要用于交易场景)都实现了 CodeGen。GaussDB 启动后会进行 LLVM 的初始化工作,检查 CPU 对 CodeGen 的支持情况,并进行环境初始化。在执行启动阶段,以表达式为例,程序会判断当前表达式是否可 JIT,是的话,则会进行 IR 函数的生成和生成定制机器码,及原本表达式执行函数的入口替代工作。
(五)内联特定函数调用
内联特定函数调用是一种优化技术,它可以在编译器级别将函数调用替换为函数体的实际代码,从而减少函数调用的开销。
分类:LLVM 内联特定函数调用可以分为静态内联和动态内联两种类型。静态内联是在编译期间进行的,编译器会根据代码的结构和上下文信息来判断是否进行内联。如果函数被标记为内联或者函数体很短小,编译器会选择将函数调用内联展开。动态内联是在运行期间进行的,编译器会生成一个内联的版本和一个非内联的版本,并在运行时根据一些条件来选择调用哪个版本。
优势:使用 LLVM 内联特定函数调用可以带来以下优势:减少函数调用开销,函数调用涉及到栈帧的创建和销毁、参数传递、返回值处理等操作,这些都会带来一定的开销。通过内联特定函数调用,可以避免这些开销,提高程序的执行效率;优化代码结构,内联特定函数调用可以将函数调用处的代码直接嵌入到调用位置,减少了函数调用的跳转和返回操作,使得代码更加紧凑和简洁;提高编译器优化能力,内联特定函数调用可以提供更多的代码上下文信息给编译器,使得编译器能够更好地进行优化,例如常量传播、死代码消除等。
应用场景:LLVM 内联特定函数调用适用于频繁调用的小型函数、循环中的函数调用和关键路径上的函数调用等场景。例如,对于一些频繁调用的小型函数,如一些简单的数学计算、字符串处理等,使用内联特定函数调用可以减少函数调用的开销,提高程序的执行效率。在循环中频繁调用的函数,使用内联特定函数调用可以减少循环迭代次数,提高循环的执行效率。对于一些关键路径上的函数调用,使用内联特定函数调用可以减少函数调用的开销,提高关键路径的执行效率。
五、展望未来

LLVM 在编译器领域的重要性不言而喻。它以其独特的设计理念、强大的功能和广泛的应用场景,成为了现代编译器基础设施的基石。
LLVM 的优势众多。首先,其语言与目标无关性使得它能够适应不同编程语言和目标平台的需求,为开发者提供了极大的便利。其次,模块化与可重用性设计促进了代码的复用和扩展,提高了开发效率。强大的优化工具能够显著提升代码的性能,支持多种编译方式则满足了不同开发阶段的需求。丰富的生态系统为开发者提供了丰富的资源和工具,进一步推动了 LLVM 的发展和应用。
展望未来,LLVM 在编译器领域的发展前景十分广阔。随着编程语言的不断发展和硬件技术的持续进步,对编译器的要求也将越来越高。LLVM 凭借其灵活的架构和强大的功能,有望在以下几个方面继续发挥重要作用。
在语言支持方面,LLVM 有望继续扩大对更多编程语言的支持。随着新兴编程语言的不断涌现,LLVM 的前端可以不断扩展,将更多的语言纳入其编译框架中,为这些语言的开发者提供高效的编译工具。
在优化技术方面,LLVM 将不断创新和改进优化算法。随着硬件架构的日益复杂,编译器需要更加智能地进行优化,以充分发挥硬件的性能。LLVM 的优化器可以结合新的硬件特性,如多核处理器、向量指令集、人工智能加速器等,提供更加高效的优化方案。
在跨平台编译方面,LLVM 将继续发挥其优势。随着移动设备、云计算和物联网的发展,跨平台编译的需求越来越大。LLVM 的可移植性和中间表示的通用性使得它能够轻松应对不同平台的编译需求,为开发者提供一致的开发体验。
在工具和生态系统方面,LLVM 的生态系统将不断壮大。更多的开发者将参与到 LLVM 的开发和扩展中,为其提供更多的工具和插件。例如,静态分析工具可以进一步提高代码的质量和安全性,JIT 编译器可以在更多的场景下发挥作用,硬件模拟和仿真工具可以为硬件设计提供更强大的支持。
总之,LLVM 作为编译器领域的璀璨之星,将在未来继续发挥重要作用。它的不断发展和创新将为编程语言的发展和硬件技术的进步提供有力的支持,为开发者带来更加高效、便捷的开发体验。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

汽车电子攻城狮

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值