使用Rust开发编译系统(C以及Rust编译的过程)

释放双眼,带上耳机,听听看~!

C以及Rust编译的过程

  • 主流的编译器

  • GCC

    • LLVM
  • C语言编译过程

  • LLVM编译过程

  • 将C源码转为LLVM IR
    * 将IR转化为BitCode
    * 将BitCode转为目标平台汇编码
    * 执行BitCode

  • Rust编译过程

  • 下一步做什么

主流的编译器

GCC

GCC编译器是由GNU开发的编译器,原名为GUN编译器,原本只能处理C语言随着发展,后续支持了C++,Java,Go等语言,所以改名为GNU编译器套件,GCC主要分为以下接口

  1. 前端接口: 将源码经过词法分析,语法分析生成与语言无关的低级中间语言表示层,然后经过优化后转化为RTL中间表示层
  2. 中间接口: 中间接口主要在RTL中间表示上进行各种优化,如循环优化,公共子表达式删除,指令重排等等
  3. 后端接口:GCC对每条RTL通过模板匹配方法调用对应的汇编模板生成汇编代码,生成的代码因处理器的不同而不同

LLVM

LLVM由C++编写,用于优化任意语言编写的程序,LLVM的命名最早源于Low Level Virtual Machine的缩写,LLVM代码有3种表示形式,IR,bitcode,汇编码,llvm提供了不同的优化Pass,对每个Pass的源码编译,得到一个Object文件,之后这些文件链接得到一个库,Pass之间由LLVM Pass管理器来统一管理
LLVM有很多其项目其中包括 LLVM Core libraries,Clang,LLD,LLDB,libc++ & libc++ ABI等等

C语言编译过程

一般的编译过程流程图大概是这样的

编译

汇编器

链接器

源代码

汇编语言

目标文件

可执行文件

但是不同的编译器有着不同的编译方式,下面我们使用LLVM对C语言编译的过程进行实践

LLVM编译过程

clang

llvm-as

llc

main.c

main.ll IR文件

main.bc bitCode

目标平台汇编码

我们开始准备LLVM的一些环境

  • llvm
  • llvm-as(Windows 安装llvm时没有这个文件,打开网站后输入llvm-as.exe搜索下载)
  • llc (同llvm-as)

首先我们创建一个test.c文件然后输入以下内容


1
2
3
4
5
6
7
8
1int mult() {
2    int a = 5;
3    int b = 3;
4    int c = a * b;
5    return c;
6}
7
8

将C源码转为LLVM IR

输入一下命令


1
2
3
1clang -emit-llvm -S test.c -o  test.ll
2
3

其中我们使用了clang作为前端进行编译,-emit-llvm用于LLVM IR写到.ll文件,-S表示仅运行预处理和编译步骤,-o参数用于将生成的内容输出到test.ll文件中

执行完毕后会在test.c同级目录下生成一个test.ll文件,将C语言代码分解为Token流(每个Token可表示标识符,字面量,运算符等等),Token流会传递给语法分析器,语法分析器使用CFG(上下文无关文法)组织成AST(抽象语法树),紧接着进行语义分析,然后生成IR

将IR转化为BitCode

我们使用一个较为简单的IR文件,内容如下


1
2
3
4
5
6
7
1// test.ll
2define i32 @mult(i32 %a, i32 %b) #0 {
3   %1 = mul nsw i32 %a, %b  
4   ret i32 %1
5}
6
7

使用命令如下


1
2
3
1llvm-as test.ll -o test.bc
2
3

我们使用llvm-as(LLVM汇编器)将LLVM IR转为BitCode,-o参数用于将生成的BitCode输出到test.bc文件中

将BitCode转为目标平台汇编码

我们使用LLVM的静态编译器LLC把BitCode转为汇编码,命令如下


1
2
3
1llc test.bc -o test.s
2
3

或者我们可以使用Clang从BitCode文件生成汇编码,命令如下


1
2
3
1clang -S test.bc -o test.s -fomit-frame-pointer
2
3

我们使用了fomit-frame-pointer参数消除帧指针,因为Clang默认不消除帧指针,但是llc却默认消除帧指针

llc命令把LLVM的BitCode编译为指定架构的汇编语言,如果命令中没有指定任何架构默认生成的本机汇编码

执行BitCode

我们把test.c的内容换为以下内容


1
2
3
4
5
6
7
8
9
10
1#include<stdio.h>
2
3int main(){
4   int num = 5;
5   printf("number is %d\n", num);
6   return 0;
7}
8
9
10

然后我们按照之前的步骤将test.c转为BitCode


1
2
3
4
1$ clang -emit-llvm -S test.c -o test.ll
2$ llvm-as test.ll -o test.bc
3
4

注: 在Windows执行第2步是会出现以下错误
llvm-as: test.ll:31:62: error: expected ‘global’ or ‘constant’
@"??_C@_0O@BAPFBKAP@number?5is?5?$CFd?6?$AA@" = linkonce_odr dso_local unnamed_addr constant [14 x i8] c"number is %d\0A\00", comdat, align 1
错误原因等待解决

最后我们使用LLI命令来运行BitCode


1
2
3
1$ lli test.bc
2
3

LLI使用LLVM bitcode格式 作为输入并且使用即时编译器(JIT)执行,如果当前的架构不 存在JIT编译器,会用解释器执行

Rust编译过程

Rust使用的是rustc进行编译,编译的过程如下

语法分析,宏扩展

类型检查

转换

LLVM

链接

RustCode

HIR

MIR

LLVM IR

.o文件

可执行程序

详细过程如下

  1. 解析输入:将.rs文件作为输入并进行解析生成AST(抽象语法树)
  2. 名称解析,宏扩展和属性配置:解析完毕后处理AST,处理#[cfg]节点解析路径,扩展宏
  3. 转为HIR:名称解析完毕后将AST转换为HIR(高级中间表示),HIR比AST处理的更多,但是他不负责解析Rust的语法,例如((1+2)+3)和1+2+3在AST中会保留括号,虽然两者的含义相同但是会被解析成不同的树,但是在HIR中括号节点将会被删除,这两个表达式会以相同的方式表达
  4. 类型检查以及后续分析:处理HIR的重要步骤就是类型检查,例如使用x.f时如果我们不知道x的类型就无法判断访问的哪个f字段,类型检查会创建TypeckTables其中包括表达式的类型,方法的解析方式
  5. 转为MIR以及后置处理:完成类型检查后,将HIR转为MIR(中级中间表示)进行借用检查以及优化
  6. 转为LLVM IR和优化:LLVM进行优化,从而生成许多.o文件
  7. 链接: 最后将那些.o文件链接在一起

我们开始实践这一过程

首先我们创建一个Cargo项目


1
2
3
1~$ cargo new complier_test
2
3

main.rs文件中的内容如下


1
2
3
4
5
1fn main(){
2    println!("Hello");
3}
4
5

将源代码转为HIR
我们可以使用cargo的 -Zunpretty参数来生成hir,rustc命令没有找到。。


1
2
3
1~/complier_test$ cargo rustc -- -Zunpretty=hir-tree -o main.hir
2
3

然后在src目录下会生成main.hir文件

将源代码转为MIR
转为mir的过程我们可以使用rustc来完成


1
2
3
1~/complier_test/src$ rustc --emit mir  -o main.mir main.rs
2
3

我们使用emit来生成emit用来生成mir,除此之外还可以使用emit来生成LLVM IR


1
2
3
1~/complier_test/src$ rustc --emit llvm-ir -o main.ll main.rs
2
3

转换为BitCode

然后我们可以使用llvm-as将IR转为BitCode


1
2
3
1~/complier_test/src$ llvm-as main.ll -o main.bc
2
3

或者我们可以使用rustc的emit参数生成


1
2
3
1~/complier_test/src$ rustc --emit llvm-bc -o main.bc
2
3

最后我们可以使用LLI来运行BitCode


1
2
3
1~/complier_test/src$ lli main.bc
2
3

下一步做什么

在下一篇文章我们使用Rust实现一个分词器,我们还需要掌握一些关于分词的理论知识

给TA打赏
共{{data.count}}人
人已打赏
安全技术

c++ list, vector, map, set 区别与用法比较

2022-1-11 12:36:11

安全经验

Tor 8.5a8 发布,更新 Firefox,修复重要安全漏洞

2019-2-16 11:12:22

个人中心
购物车
优惠劵
今日签到
有新私信 私信列表
搜索