17611538698
webmaster@21cto.com

LLVM:Swift、Rust、Clang 等语言的背后力量

编程语言 1 1594 2023-08-23 03:39:55

21CTO导读:LLVM 是一个新型编译器框架,用于将语言代码生成本机代码。可用它来开发并推出新的编程语言,也可以增强现有编程语言。


目录

  • 什么是 LLVM

  • LLVM与GCC的区别

  • LLVM 专为可移植性而设计

  • 编程语言如何使用 LLVM

  • 如何使用 LLVM

  • LLVM 不做什么


图片

近几年,新编程语言层出不穷,现有的语言改进也在不断发生,编程语言的开发环境正在日趋成熟。


包括 Mozilla 的 Rust、苹果的 Swift、JetBrains 的 Kotlin以及实验性 Python 变体 Mojo为开发者、工程师们提供了速度、安全性、便利性、可移植性和功能方面的广泛选择。


用于构建编程语言的新工具,特别是编译器的出现,是产生这种丰富性的一个重要因素之一。


其中强大也是主流的编译器便是LLVM(http://llvm.org/)。这是一个开源项目,最初由伊利诺伊大学的 Swift 语言创建者 Chris Lattner 开发。


图片

LLVM官网(https://www.llvm.org)

LLVM 是低级虚拟机的缩写。它指的是一种称为 LLVM 项目的编译技术,它是模块化、可重用的编译器和工具链技术的集合。LLVM 项目已经超出了其最初的想法范围,因为该项目不再专注于传统虚拟机。


LLVM 可以创建新的编程语言,还可以增强现有语言的特性。它提供给开发者一些工具,可创建一门新语言,特别是最吃力不讨好的那部分,比如:开发编译器、将输出的代码移植到多个平台与架构、生成特定于架构的优化(例如向量化)以及编写代码来处理常见语言的复杂之处(例如异常处理) 。


此外,LLVM的开源许可证相当开放、自由,它可以作为软件组件自由重用,也可作为服务部署。


使用 LLVM 的编程语言包有包括苹果的 Swift 语言,它使用 LLVM 作为其编译框架,Rust 语言将它作为 Rust 工具链的核心组件。


还有许多编译器都使用 LLVM,包括 Clang、C/C++ 编译器(因此得名“C-lang”),还有Mono,.NET 语言实现,也可以选择使用 LLVM 后端编译为本机代码。Kotlin 名义上是一种 JVM 语言,提供了一种名为Kotlin/Native 的编译器技术,该技术实质上使用 LLVM 编译为本机代码。


什么是 LLVM?


LLVM 是一个用于以编程方式创建本机机器代码的库。开发人员使用其 API, 以其称为中间表示(IR)的格式生成相关指令。然后 LLVM 可以将 IR 编译为独立的二进制文件,对代码执行 JIT(即时)编译,这样就可在运行时环境(例如该语言的解释器或运行时)的上下文中正常运行。


LLVM 的 API 提供了用于开发编程语言中常见结构和模式的原语。


比如,几乎每种编程语言都有函数和全局变量的概念,并且许多语言都有协程和 C 语言的外部函数接口。LLVM 将函数和全局变量作为其 IR 中的标准元素,并且具有创建协程与 C 语言库接口。


语言开发者无需花费时间再重新发明轮子,只需使用 LLVM 的实现并专注于语言中的特色,更多关注这个部分即可。


图片
LLVM的新语言实现视图对比


上图就是 LLVM 中间表示 (IR) 的示例。
右侧是一个简单的C语言程序;左侧是由 Clang 编译器翻译成 LLVM IR 的等价源代码。


LLVM与GCC的区别


LVM 和 GNU 编译器集合 (GCC) 都是编译器。不同之处在于 GCC 支持多种编程语言,而 LLVM 不是任何给定语言的编译器。LLVM 是一个从任何类型的源代码生成目标代码的框架。

虽然 LLVM 和 GCC 都支持多种语言和库,但它们的许可证和开发方式不同。LLVM 库的许可证更加自由,而 GCC 对其重用性有非常多的限制。

就性能差异而言,GCC 过去一直被认为更胜一筹。但 LLVM 正在取得更大进展。


LLVM 专为可移植性而设计


要充分理解 LLVM,我们将它 与 C 语言进行比较会有所帮助。


C 语言被描述为可移植的高级汇编语言。这是因为 C 语言的结构可以与系统硬件紧密映射,并且它已经被移植到几乎所有系统架构中。但 C 语言作为可移植汇编语言的用处仍然有一定限制,它必竟不是为此目标而专门设计的语言。


相比之下,LLVM 的 IR 从一开始就被设计为便携式组件。提供独立于任何特定机器架构的原语,是实现这种可移植性的重要方法。例如,整数类型不限于底层硬件的最大位宽度(例如 32 或 64 位)。你可以根据需要使用任意数量的位来创建原始整数类型,例如 128 位整数,也不必担心还需要精心输出以匹配特定处理器的指令集,LLVM 会为处理好所有这些事情。


LLVM 的架构中立式的设计,使它更容易支持当前和未来的各种硬件。


例如,IBM 贡献了代码来支持其 z/OS、Linux on PowerPC(包括对 IBM 的 MASS 矢量化库的支持)以及用于 LLVM 的 C、C++ 和 Fortran 项目的 AIX 架构支持。 


想查看 LLVM IR 的实时示例,可以访问Godbolt Compiler Explorer 站点,并将 C 或 C++ 翻译为 LLVM IR。


图片

选择 Clang 作为编译器,然后从Add new中选择LLVM IR打开一个选项卡,其中显示从提供的 C/C++ 代码生成 LLVM IR 代码。


在语言中如何使用 LLVM


LLVM 最常见的场景用例是作为提前 (AOT) 语言编译器。例如,Clang 项目提前将 C 和 C++ 编译为本机二进制文件。


图片


LLVM 也让其它事情成为可能,我们接着往下看。


使用 LLVM 进行即时编译


有些情合需要在运行时动态生成代码,而不是提前编译。


例如,  Julia 语言对其代码进行 JIT 编译,因为它需要快速运行并通过 REPL(读取(read)-评估(eval)-打印(print)-循环(loop))或以交互式提示符同用户交互。


Numba是 Python 的数学精算加速包,可将选定的 Python 函数 JIT 编译为机器代码。它还可以提前编译 Numba 修饰的代码,补充像 Python 作为解释性语言提供快速开发的缺点,使用 JIT 编译生成代码后比提前编译更好地补充了 Python 的交互式工作流程。


还有一些人正在尝试使用 LLVM 作为 JIT 编译器的新方法,例如编译 PostgreSQL 查询,从而将性能提高 5 倍以上。


图片

LLVM扩展Python即时编译


Numba 使用 LLVM 即时编译数字代码并加速其执行。JIT 加速sum2d 函数的执行速度比常规 Python 代码快约 139 倍。


使用 LLVM 自动代码优化


LLVM 不仅仅将 IR 编译为本机机器代码。开发者还可以通过编程方式指示它在整个链接过程中以高粒度优化代码。


优化可能非常高强度,包括内联函数、消除死代码(包括未使用的类型声明和函数参数)以及展开循环等


它的优势力量在于不必自己实现这一切。LLVM 可以为开发者处理这些任务,你也可以指示它根据需要关闭和打开优化。例如,如果你希望以牺牲一些性能为代价获得更小的二进制文件,可以让编译器前端告诉 LLVM 禁用循环展开。


使用 LLVM 的特定领域编程语言


LLVM 已被用来为许多通用语言生成编译器,但它对于生成高度垂直或专用于问题域的语言也非常有价值。


在某种角度讲,这是 LLVM 最耀眼的亮点,因为它消除了创建通用语言的许多苦差事,并使其表现的更加良好。


例如,Emscripten 项目采用 LLVM IR 代码并将其转换为 JavaScript,理论上允许任何具有 LLVM 后端的语言导出可以在浏览器中运行的代码。这项工作的长期成果之一是基于 LLVM 的后端,可以生成WebAssembly,这样 Rust 等语言的目标可以直接编译为 WASM 。

使用 LLVM 的另一种方式是向现有语言添加特定于域的扩展。

Nvidia 使用 LLVM 创建Nvidia CUDA 编译器,它允许语言添加对 CUDA 的本机代码支持,该支持作为你生成的本机代码的一部分进行更快编译,而不是通过附带的库慢速调用。

LLVM 在特定领域语言方面的成功激发了 LLVM 内的新项目,来解决它们所产生的其它问题。目前存在的问题是,如果前端没有做大量的工作,一些 DSL 很难转化为 LLVM IR。

目前,LLVM正在进展中的一种解决方案是多级中间表示(MLIR)项目。

MLIR 提供了表示复杂数据结构和操作的便捷方法,然后可以自动将其转换为 LLVM IR。例如,TensorFlow 机器学习框架可以使用 MLIR 将其许多复杂的数据流图操作有效地编译为本机代码。

如何使用 LLVM


使用 LLVM 的典型方法是通过支持 LLVM 库的语言编写代码。


两种常见的语言选择是 C 和 C++。许多 LLVM 开发人员默认选择这两者之一,原因如下: 


  • LLVM 本身是用 C++ 编写的。

  • LLVM 的 API 可用于 C 和 C++ 版本。

  • 许多语言开发基本以 C/C++ 为基础。


不过,这两种语言并不是唯一选择。许多编程语言都可以本地调用 C 库,因此理论上可以使用任何此类语言执行 LLVM 开发。但拥有一个能够优雅地封装 LLVM API 的实际库会有所帮助。

幸运的是,许多语言和语言运行时都有这样的库,包括 C#/.NET/Mono、Rust、Haskell、OCAML、Node.js、Go和Python等。

需要注意的是,某些与 LLVM 的语言绑定可能不如其他语言完整。例如,随着时间的推移,Python 出现了许多选择,其完整性和实用性各不相同,并且出现了一个强大竞争者。

  • llvmlite由创建 Numba 的团队开发,已成为当前在 Python 中使用 ,它成为LLVM 的竞争者。但它仅实现 LLVM 功能的一个子集,使用与否具体取决于 Numba 项目的需求。该子集提供了 LLVM 用户所需的绝大多数内容。为此,llvmlite通常是在 Python 中使用 LLVM 的最佳选择。

  • LLVM 项目有自己的一组C API 的绑定,目前也尚未完全维护它们。

  • llvmpy是第一个流行的 LLVM Python 绑定,但于 2015 年停止维护。这对任何软件项目来说都是不好的消息,但考虑到每个新版本中出现的大量更改,使用 LLVM 时情况更加复杂。

  • llvmcpy 旨在使 C 库的 Python 绑定保持最新,以自动方式更新它们,并使用 Python 的本机习惯用法来访问它们。llvmcpy目前可以使用 LLVM API 进行一些基本工作,但从 2019 年以来到现未出现更新。


如果开发者对如何使用 LLVM 库构建语言感到好奇,LLVM 的创建者有一个教程,使用 C++ 或 OCAML,指导你创建一种名为 Kaleidscope 的简单编程语言。它目前已被移植到其它真实世界的编程语言,包括:

  • Haskell:原始教程的直接移植。

  • Python:其中一个移植紧密遵循教程,而另一个则是使用交互式命令行进行更强大的重写。它们都用作llvmlite LLVM 的绑定。

  • Rust 和 Swift:似乎不可避免地将教程移植到 LLVM 帮助诞生的两种语言。


最后,LLVM 教程还提供人类自然语言版本,目前它已被翻译成中文,使用原始的 C++ 和 Python。

LLVM 不做什么


了解完了 LLVM 提供的所有功能,我们了解它不做什么也很有价值。


例如,LLVM 并不解析语言的语法。现在许多工具已经可以完成这项工作,例如 lex/yacc、flex/bison、Lark和 ANTLR。不管怎么样,解析都应该与编译分离,因此, LLVM 不去解决这些问题也就不足为奇了。


LLVM 也不直接解决围绕指定语言更大的范畴,比如软件文化。安装编译器的二进制文件、管理安装中的包以及升级工具链——这些,统统需要我自己搞定这些全部工作。

最后一点,也是最重要的,LLVM 仍然没有为其语言公共部分提供「原语」。许多编程语言都有某种方式的垃圾收集和内存管理,要么作为管理内存的主要方式,要么作为RAII(C++ 和 Rust 使用的策略)等策略的辅助手段。LLVM 没有提供垃圾收集器机制,但它提供了实现垃圾收集的工具 ,允许用元数据标记代码,从而使编写垃圾收集器变得更容易。

但是,以上所有这些都不能排除 LLVM 最终添加本机机制来实现垃圾收集的可能性。

LLVM 还在快速发展,每6个月左右就会发布一个主版本。目前许多当前语言将 LLVM 置于其开发过程的核心,它的开发速度只会加快,不会减缓。

以上,希望助大家理解LLVM。

作者:场长
参考:
https://www.infoworld.com/article/3247799/what-is-llvm-the-power-behind-swift-rust-clang-and-more.html
https://www.heavy.ai/technical-glossary/llvm

评论