文章推荐收藏:LLVM基础架构和Rust
LLVM Infrastructure and Rust
https://www.bexxmodd.com/log/llvm-infrastructre-and-rust/7
一、摘要:
前言
LLVM是许多编程语言背后的引擎。它被用来编译C、C++、Rust、Go、Swift等语言。这篇文章与LLVM相关,将探索以下主题:
LLVM基础架构是什么
LLVM如何工作
LLVM程序结构
LLVM和Rustc
LLVM基础架构是什么
LLVM基础架构是一组模块化和可重用的编译器和工具链技术。LLVM现包含几个子项目如Clang、C++、Objective-C编译器,调试器LLDB、libc++等。
LLVM编译器框架是一个模块化和可重用的编译器框架,使用LLVM基础架构提供端到端代码编译。它用于构建、优化、安全检查和生成中间代码(IR)或二进制(机器)代码(BC)。
LLVM允许将上层编程语言代码转换为中间表示(IR),后者可以转换为任何给定硬件架构的二进制代码(BC)。LLVM IR语言独立于源语言和目标语言。
LLVM如何工作
正如前面提到,LLVM是一个模块化和可重用的编译器框架,支持多个前端和后端。编写程序的生命周期包括编写源代码,然后将其编译成二进制代码以便执行。我们感兴趣的是编译阶段。当源代码被编译成二进制时,它将按照后续的顺序经过如下图所示的几个步骤。
当源代码转换成LLVM IR时,它可以采用三种格式之一。内存内格式是一种适合于编译过程中前端部分的二进制表示。其他两种格式是位码Bitcode(.bc)和汇编Assembly(.ll)。位码是一种二进制磁盘存储格式,适合快速加载,因为它的内存效率更高。汇编是一种便于人类阅读的文本格式。
你可能想知道为什么要使用IR/BC而不是原生汇编格式和本地原生二进制格式?
原因是原生汇编格式与平台的机器码有一对一的绑定,这取决于目标的架构例如针对X86的程序集和针对ARM的程序集是不同的。原生汇编代码通过汇编程序转换为主机原生二进制文件,LLVM也包含这一特性。
LLVM程序结构
LLVM IR系统,就像任何其他系统一样,有它自己程序结构。
顶层容器是一个Module,它对应于前端编译器的每个翻译单元。模块可以由一个或多个函数组成。每个函数有一个或多个基础块,这些基础块包含有指令。指令是单行的,在IR语言中有多种指令可用。
在IR描述中,会遇到两种变量,局部变量,用%符号表示;全局变量,用@符号表示。
LLVM IR可以使用无限数量的临时寄存器,而不是像原生汇编程序那样只使用预定义数量的寄存器。
IR寄存器是由整数来定义比如1、2、3、..N。例如%2 = load i32, i32* %x表示存储在本地变量x地址的值被加载到临时寄存器2中。
LLVM IR另一个优点是它利用所谓的静态单赋值(Static Single Assignment (SSA))形式。这允许对代码进行各种优化。常规变量可以被重新赋值多次,而SSA中变量只能被赋值一次(注意这里不包括同一变量使用多次store指令)。这使得编译器可以进行更有效的寄存器分配,因为在任何给定的点上都可以很容易地近似推导多个活动变量。它可以检测未使用的变量,并防止程序分配不必要的指针。
例如,下面的代码x = x * x在SSA中表示为x_2 := x_1 * x_1。如果我们进入变量不断得到新值的循环中,SSA使用称为Phi的节点,这些Phi节点将把变量合并到最终的值中,最终的值将从分支或循环中返回或输出。
LLVM和Rustc
Rustc官方开发指南列出了他们为什么使用LLVM的原因:
我们不需要编写完整的编译器后端。这减少了实现和维护负担。
我们从LLVM项目收集的高级优化组件中获益。
我们可以自动将Rust编译到LLVM支持的任何平台上。例如只要LLVM添加对wasm的支持,Rust, Clang和许多其他语言都可以编译成wasm。
我们和其他编译器项目互相受益。例如当发现Spectre和Meltdown安全漏洞时,只需要修补LLVM。
解读Rust示例代码为何有这样的IR输出
作者对上面Rust代码如何生成IR进行较详细解读,比如:factorial函数中如何使用SSA和Phi节点来分配使用临时变量等,感兴趣的朋友可进一步深入学习了解;
二、读后感及推荐:
1、这篇文章深入浅出介绍LLVM基础架构及其特点,较详细解读Rust示例代码对应生成的LLVM IR含义及要点,特别适合入门理解;
关键词: LLVM基础架构和Rust