使用 LLVM 及其中间表示构建一个自定义编译器
LLVM(之前称为低级虚拟机)是一种非常强大的编译器基础架构框架,专门为使用您喜爱的编程语言编写的程序的编译时、链接时和运行时优化而设计。LLVM 可运行于若干个不同的平台之上,它以能够生成快速运行的代码而著称。
LLVM 框架是围绕着代码编写良好的中间表示 (IR) 而构建的。本文(由两部分组成的系列文章的第一部分)将深入讲解 LLVM IR 的基础知识以及它的一些微妙之处。在这里,您将构建一个可以自动为您生成 LLVM IR 的代码生成器。拥有一个 LLVM IR 生成器意味着您所需要的是一个前端以供插入您所喜爱的编程语言,而且这还意味着您拥有一个完整的流程(前端解析器 + IR 生成器 + LLVM 后端)。创建一个自定义编译器会变得更加简单。
开始使用 LLVM
在开始之前,在您的开发计算器上必须已经拥有已编译好的 LLVM(参阅 参考资料 获取相关链接)。本文中的示例均基于
LLVM V3.0。对于 LLVM 代码的后期生成和安装,最重要的两个工具是 llc
和 lli
。
llc 和 lli
因为 LLVM 是一个虚拟机,所以它可能应该拥有自己的中间字节代码表示,不是吗?最后,您需要将 LLVM 字节代码编译到特定于平台的汇编语言中。然后您才能通过本机汇编程序和链接器来运行汇编代码,从而生成可执行的共享库等。您可以使用 llc
将
LLVM 字节代码转换成特定于平台的汇编代码(请参阅 参考资料,获取关于此工具的更多信息的链接)。对于
LLVM 字节代码的直接执行部分,不要等到在本机执行代码崩溃后才发现您的程序中有一个或两个 bug。这正是 lli
的用武之地,因为它可以直接执行字节代码。lli
可以通过解释器或使用高级选项中的即时
(JIT) 编译器执行此工作。请参阅 参考资料,获取关于 lli
的更多信息的链接。
llvm-gcc
llvm-gcc 是 GNU Compiler Collection (gcc) 的修改版本,可以在使用 -S
-emit-llvm
选项运行时会生成 LLVM 字节代码。然后您可以使用 lli
来执行这个已生成的字节代码(也称为 LLVM
汇编语言)。有关 llvm-gcc 的更多信息,请参阅 参考资料。如果您没有在自己的系统中预先安装
llvm-gcc,那么您应该能够从源代码构建它,请参阅 参考资料,获取分步指南的链接。
使用 LLVM 编写 Hello World
要更好地理解 LLVM,您必须了解 LLVM IR 及其微妙之处。这个过程类似于学习另一种编程语言。但是,如果您熟悉 C
语言和 C++
语言以及它们的一些语法怪现象,那么在了解
LLVM IR 方面您应该没有太大的障碍。清单 1 给出了您的第一个程序,该程序将在控制台输出中打印
"Hello World"。要编译此代码,您可以使用 llvm-gcc。
清单 1. 看起来非常熟悉的 Hello World 程序
#include <stdio.h> int main( ) { printf("Hello World! "); }
要编译此代码,请输入此命令:
Tintin.local# llvm-gcc helloworld.cpp -S -emit-llvm
完成编译后,llvm-gcc 会生成 helloworld.s 文件,您可以使用 lli
来执行该文件,将消息输出到控制台。lli
的用法如下:
Tintin.local# lli helloworld.s Hello, World
现在,先看一下 LLVM 汇编语言。清单 2 给出了该代码。
清单 2. Hello World 程序的 LLVM 字节代码
@.str = private constant [13 x i8] c"Hello World! 0", align 1 ; define i32 @main() ssp { entry: %retval = alloca i32 %0 = alloca i32 %"alloca point" = bitcast i32 0 to i32 %1 = call i32 @puts(i8* getelementptr inbounds ([13 x i8]* @.str, i64 0, i64 0)) store i32 0, i32* %0, align 4 %2 = load i32* %0, align 4 store i32 %2, i32* %retval, align 4 br label %return return: %retval1 = load i32* %retval ret i32 %retval1 } declare i32 @puts(i8*)
理解 LLVM IR
LLVM 提供了一个详细的汇编语言表示(参阅 参考资料 获取相关的链接)。在开始编写我们之前讨论的自己的 Hello World 程序版本之前,有几个需知事项:
-
LLVM 汇编语言中的注解以分号 (
;
) 开始,并持续到行末。 -
全局标识符要以
@
字符开始。所有的函数名和全局变量都必须以@
开始。 -
LLVM 中的局部标识符以百分号 (
%
) 开始。标识符典型的正则表达式是[%@][a-zA-Z$._][a-zA-Z$._0-9]*
。 -
LLVM 拥有一个强大的类型系统,这也是它的一大特性。LLVM 将整数类型定义为
iN
,其中 N 是整数占用的字节数。您可以指定 1 到 223- 1 之间的任意位宽度。 -
您可以将矢量或阵列类型声明为
[no. of elements X size of each element]
。对于字符串 "Hello World!",可以使用类型[13 x i8]
,假设每个字符占用 1 个字节,再加上为 NULL 字符提供的 1 个额外字节。 -
您可以对 hello-world 字符串的全局字符串常量进行如下声明:
@hello = constant [13 x i8] c"Hello World! 0"
。使用关键字constant
来声明后面紧跟类型和值的常量。我们已经讨论过类型,所以现在让我们来看一下值:您以c
开始,后面紧跟放在双引号中的整个字符串(其中包括