生成中间代码IR(intermediate representation)

发布时间 2023-05-21 04:34:32作者: 吴建明wujianming

 完成以上步骤后就开始生成中间代码IR了,代码生成器(Code Generation)会将语法树自顶向下遍历逐步翻译成LLVM IR。OC代码在这一步会进行runtime的桥接,比如property合成、ARC处理等。

IR的基本语法
@ 全局标识
% 局部标识
alloca 开辟空间
align 内存对齐
i32 32个bit,4个字节
store 写入内存
load 读取数据
call 调用函数
ret 返回
通过下面命令,可以生成.ll的文本节件,查看IR代码。
clang -S -fobjc-arc -emit-llvm main.m
1. IR的优化
在上面的IR代码中,可以看到,通过一点一点翻译语法树,生成的IR代码,看起来有点蠢,其实是可以优化的。
LLVM的优化级别分别是-O0、-O1、-O2、-O3、-Os、-Ofast、-Oz(第一个是大写英文字母O)。
可以使用命令进行优化:
clang -Os -S -fobjc-arc -emit-llvm main.m -o main.ll
优化后的IR代码,简洁明了(优化等级并不是越高越好,release模式下为-Os,这也是最推荐的)。
也可以在 xcode 中设置:target -> build Setting -> Optimization Level
2. bitCode
Xcode 7以后,如果开启bitcode,苹果会对.ll的IR文件做进一步的优化,生成.bc文件的中间代码。
通过下面命令,使用优化后的IR代码生成.bc代码:
clang -emit-llvm -c main.ll -o main.bc
3. 后端阶段(生成汇编.s)
后端将接收到的IR结构转化成不同的处理对象,并将其处理过程实现为一个个的Pass类型。通过处理Pass,来完成对IR的转换、分析和优化。然后生成汇编代码(.s)。
通过下面命令,使用.bc或者.ll代码生成汇编代码:
// bitcode -> .sclang -S -fobjc-arc main.bc -o main.s// IR -> .sclang -S -fobjc-arc main.ll -o main.s// 也可以对汇编代码进行优化 clang -Os -S -fobjc-arc main.ll -o main.s
4. 汇编阶段(生成目标文件.o)
目标文件的生成,是汇编器以汇编代码作为输入,将汇编代码转换为机器代码,最后输出目标文件(.o)。
命令如下:
clang -fmodules -c main.s -o main.o
通过nm命令,查看main.o中的符号:
xcrun nm -nm main.o
可以看到执行命令后,报了一个错:找不到外部的_printf符号。因为这个函数是从外部引入的,需要将使用的对应的库链接进来。
5. 链接阶段(生成可执行文件Mach-O)
链接器把编译产生的.o文件、需要的动态库.dylib和静态库.a链接到一起,生成可执行文件(Mach-O文件)。
命令如下:
clang main.o -o main
查看链接之后的符号:
baypac@JustindeiMac Clang % xcrun nm -nm main
(undefined) external _printf(from libSystem)
(undefined) external dyld_stub_binder(from libSystem)
(__TEXT, __text) [referenced dynamically] external __mh_execute_header
(__TEXT, __text) external _test
(__TEXT, __text) external _main
(__DATA, __data) non-external __dyld_private
可以看到输出结果中依然显示找不到外部符号_printf,但是后面多了(from libSystem),指明了_printf所在的库是libSystem。这是因为libSystem动态库需要在运行时动态绑定。
test函数和main函数也已经生成了文件的偏移位置。目前这个文件已经是一个正确的可执行文件了。
同时还多了一个dyld_stub_binder符号,其实只要链接就会有这个符号,这个符号是负责动态绑定的,在Mach-O进入内存后(即执行),dyld立刻将libSystem中dyld_stub_binder的函数地址与Mach-O中的符号进行绑定。
dyld_stub_binder符号是非懒绑定。其他的懒绑定符号,比如此处的_printf,在首次使用的时候通过dyld_stub_binder来将真实的函数地址与符号进行绑定,调用的时候就可以通过符号找到对应库里面的函数地址进行调用了。
外部函数绑定图解:
链接和绑定的区别:
1)链接,编译时,标记符号在哪个库,只是做了一个标记。
2)绑定,运行时,将外部函数地址与Mach-O中的符号进行绑定。
使用如下命令执行Mach-O文件:
./main
6. 绑定硬件架构
根据不同的硬件架构(此处是M1版iMAC,arm64),生成对应的可执行文件。
7. 各阶段使用的命令
\
//// ====== 前端 开始=====
// 1. 词法分析 clang -fmodules -fsyntax-only -Xclang -dump-tokens main.m
// 2. 语法分析
clang -fmodules -fsyntax-only -Xclang -ast-dump main.m // 3. 生成IR文件 clang -S -fobjc-arc -emit-llvm main.m
// 3.1 指定优化级别生成IR文件
clang -Os -S -fobjc-arc -emit-llvm main.m -o main.ll
// 3.2 (根据编译器设置) 生成bitcode 文件
clang -emit-llvm -c main.ll -o main.bc
 
//// ====== 后端 开始=====
 
// 1. 生成汇编文件
// bitcode -> .s
clang -S -fobjc-arc main.bc -o main.s
// IR -> .s
clang -S -fobjc-arc main.ll -o main.s
// 指定优化级别生成汇编文件
clang -Os -S -fobjc-arc main.ll -o main.s
 
// 2. 生成目标Mach-O文件
clang -fmodules -c main.s -o main.o
// 2.1 查看Mach-O文件
xcrun nm -nm main.o
 
// 3. 生成可执行Mach-O文件
clang main.o -o main
 
//// ====== 执行 开始=====
// 4. 执行可执行Mach-O文件
./main
生成汇编文件就已经是编译器后端的工作了,为什么还是使用的clang命令呢?这是因为使用clang提供的接口调起后端相应的功能。至于后端有没有自己特有的命令,就不知道。