博客 阅读时间 6 分钟

Chris Lattner:革新 C++ 世界的人

分享本文
Chris Lattner: Revolutionizing the C++ World

几乎所有 C++ 开发者大概都知道 LLVM 基础设施和 Clang 编译器。但有多少人知道 Chris Lattner 是它们的创造者,而且当时他只有 25 岁?这怎么可能?我记得我 25 岁的时候,时间都花在努力理解 C++ 基础知识上了 :)

故事始于一篇论文

2000 年底,Lattner 以研究助理和硕士生的身份加入伊利诺伊大学厄巴纳-香槟分校。在与 Vikram Adve 合作期间,他设计并开始实现 LLVM——一种创新的优化编译器基础设施,这成为他 2002 年硕士论文的主题。他于 2005 年完成博士学位,研究优化指针密集型程序的新技术,并将它们加入 LLVM。

以下是他论文摘要中阐述的 LLVM 设计动机:

现代编程语言和软件工程原则正在给编译器系统带来越来越多的问题。使用简单的"编译-链接-执行"模型的传统方法,已无法在新条件的要求下提供足够的应用性能。传统的过程间和按配置驱动的编译方法虽然能提供所需的应用性能,但需要多到不可行的编译时间来构建应用。本论文提出 LLVM:一种编译器基础设施的设计与实现,支持独特的多阶段优化系统。该系统旨在支持广泛的过程间和按配置驱动的优化,同时保持足够的效率,可用于商业编译器系统。LLVM 虚拟指令集是将整个系统粘合在一起的纽带。它是一种底层表示,但带有高层类型信息。这既提供了底层表示的好处(紧凑的表示、种类繁多的可用变换等),又提供高层信息,以支持在链接时和链接后进行激进的过程间优化。特别是,该系统被设计为支持实地优化,既可以在运行时进行,也可以在机器其他空闲时间进行。

简而言之,LLVM 背后的理念是使用 LLVM 中间表示(IR),它类似于 Java 字节码或 .NET IL。

LLVM IR 旨在承载编译器优化器部分中的中层分析和变换。它在设计时考虑了许多具体目标,包括支持轻量级运行时优化、跨函数/过程间优化、全程序分析以及激进的重组变换等。不过,它最重要的一点是:它本身被定义为一门具有明确语义的一等语言。

让我们考虑一个相对简单的函数,它接受三个整数参数并返回它们的算术组合。

int mul_add(int x, int y, int z) {
  return x * y + z;
}

这个函数的 LLVM IR 如下所示:

define i32 @mul_add(i32 %x, i32 %y, i32 %z) {
entry:
  %tmp = mul i32 %x, %y
  %tmp2 = add i32 %tmp, %z
  ret i32 %tmp2
}

回到本论文的重点——优化。要解释这个编译阶段,我说得再好也不如 LLVM 之父 Chris Lattner 在这篇文章所述:

"为了直观地说明优化是如何工作的,最好通过一些例子来讲解。编译器优化有许多不同的种类,因此很难为解决任意问题提供一个通用的配方。尽管如此,大多数优化都遵循一个简单的三段式结构:

  • 寻找要被变换的模式。
  • 验证该变换对匹配的实例是否安全/正确。
  • 执行变换,更新代码。
优化器读入 LLVM IR,对其进行一番处理,然后输出有望执行得更快的 LLVM IR。在 LLVM 中(与许多其他编译器一样),优化器被组织为由不同优化遍(pass)组成的流水线,每一遍都在输入上运行并有机会做些事情。常见的优化遍包括内联器(将函数体替换到调用点)、表达式重结合、循环不变量代码外提等。根据优化级别的不同,运行的优化遍也不同:例如在 -O0(无优化)时,Clang 编译器不运行任何优化遍;在 -O3 时,其优化器会运行多达 67 个优化遍(截至 LLVM 2.8)。

LLVM 和 Clang 作为构建工具的基础设施

Clang 的一个主要设计理念是其基于库的架构。在这种设计中,前端的各个部分可以被清晰地划分为独立的库,然后可以根据不同的需求和用途进行组合。此外,基于库的方式鼓励良好的接口设计,并使新开发者更容易参与进来(因为他们只需要理解大图景中的小块部分)。

llvm1

LLVM/Clang 的设计使其成为构建工具的强大基础设施,这得益于其基于库的架构,它让组件复用和将新特性集成到其他项目中变得更加容易。

许多工具都基于 LLVM 和 Clang,包括 clang-tidy、clang-query、CppDepend 等等。

Clang 始终紧跟新标准

Clang 始终与新标准保持同步;此外,它总是为即将到来的特性提供实验性支持。当前版本对 C++17 之后的 C++ 标准(暂定名 C++2a)的一些拟议特性提供实验性支持。请注意,随着 C++2a 标准草案的演进,对这些特性的支持可能会在不另行通知的情况下变更或移除。

您可以使用-std=c++2a选项在 C++2a 模式下使用 Clang。

语言特性 C++2a 提案 Clang 中可用?
位域的默认成员初始化器 P0683R1 Clang 6
const&限定的成员指针 P0704R1 Clang 6
允许lambda 捕获 [=, this] P0409R2 Clang 6
__VA_OPT__用于预处理器逗号省略 P0306R4 Clang 6
指定初始化器 P0329R4 部分支持(扩展)
类模板实参推导中的初始化列表构造函数 P0702R1 Clang 6
特化上的访问检查 P0692R1 部分支持

Clang 现在已被许多大公司采用,包括谷歌、微软,当然还有从一开始就使用它的苹果。

LLVM 基础设施可用于实现许多编程语言的编译器和解释器。例如,PostgreSQL 现在使用 LLVM JIT 来优化查询执行,正如这里所述:

目前,PostgreSQL 使用解释器执行 SQL 查询,这相当慢。不过,通过使用 LLVM JIT"即时"编译查询可以实现显著的提速:通过这种方式,可以为给定的 SQL 查询生成更高效的代码,这些代码使用运行时已知的信息进行专门化,然后再用 LLVM 进一步优化。这种方法对于复杂查询尤其重要,因为此时性能受 CPU 限制。

感谢所有 LLVM/Clang 开发者,特别感谢 Chris Lattner,他发起了这场革命,为我们提供了开发有意义工具的强大基础设施。

最后,给年轻开发者们的一句话:成为下一个 Chris Lattner,在编程世界发起一场革命吧 :)

分享本文