1.传统编译器设计
1.1编译器前端
编译器前端的任务是解析源代码。它会进行词法分析、语法分析、语义分析、检查代码中是否存在错误,然后构建抽象语法树(Abstract Syntax Tree ,缩写为 AST),然后成成中间代码( Intermediate Representation,缩写为IR)。
1.2优化器
优化器负责各种优化,缩小包的体积(剥离符号 ) 、改善代码的运行时间( 消除冗余计算、减少指针跳转次数等)。
1.3后端,代码生成器
后端将代码映射到目标指令集,生成机器语言,并且进行机器相关的代码优化。
由于传统的编译器(如GCC) 是为整体的应用程序设计的,不支持多种语言或者多种硬件架构,所以他们的用途收到了很大的限制。
2.LLVM的设计
LLVM 是编译器的架构系统,C++ 编写而成的。用于优化以任意语言编写的程序的编译时间(compile-time)、连接时间(link-time)、运行时间(run-time)、空闲时间 (idle-time)。
LLVM设计中最重要的设计是使用了通用的代码表示形式(IR), 在需要支持一种新的语言时 , 只需要再编写一个可以产生IR的 独立 前端; 需要支持一种新的硬件架构时 , 只需要再编写一个可以接收IR的 独立后端 。
3.Clang
Clang
是一个由 Apple
主动编写,是LLVM项目中的一个子项目。基于 LLVM
的轻量级编译器,之初是为了替代GCC,提供更快的编译速度。他是负责编译C、C++、OC
语言的编译器 。
3.1 编译流程
可以通过下面的命令打印源码的编译过程:
clang -ccc-print-phases main.m
打印结果如下:
整个过程中,没有明确指出优化器,是因为优化已经分布在前后端里面了。
接下来对每个步骤,详细分析:
0: 输入源文件:
找到源文件
1:预处理阶段:
执行预处理指令,包括进行宏替换、头文件的导入、条件编译,产生新的源码给到编译器.可以通过命令clang -E main.m
,看到执行预处理指令后的代码。
2:编译阶段 -> IR(.ll) 或者 bitcode(.bc)文件:
进行词法分析、语法分析、语义分析、检测语法是否正确、生成AST、生成IR(bitcode)
2.1 词法分析:
预处理完成后,进行词法分析,将代码分隔成一个个的Token及标明其所在的行数和列数,包括关键字、类名、方法名、变量名、括号、运算符等
使用下面的命令,可以看到词法分析后的结果:
clang -fmodules -fsyntax-only -Xclang -dump-tokens main.m
2.2语法分析
词法分析后,是语法分析,它的任务是验证源程序的语法结构的正确性,在词法分析的基础上,将单词组合成各类语法短语,如语句、表达式等。然后将所有节点组成抽象语法树(AST)。
通过下面的命令,可以查看语法分析后的结果:
clang -fmodules -fsyntax-only -Xclang -ast-dump main.m
// 如果导入头文件找不到,可以指定SDK
clang -isysroot sdk路径 -fmodules -fsyntax-only -Xclang -ast-dump main.m
2.3 生成IR
将语法树自顶向下遍历逐步翻译成LLVM IR。OC 代码在这一步会进行运行时的处理,比如 分类属性的合成、ARC处理等
通过下面的命令可以生成 .ll 的文本文件,查看IR代码:
clang -S -fobjc-arc -emit-llvm main.m
上面IR代码的意思是:
- test 方法,输入两个参数 %0, %1
- 创建两个变量 %3,%4
- 将%0的数据写入到%3中,将%1的数据写入到%4中
- 读取%3的数据并赋值给%5,读取%4的数据并赋值给%6
- 将%5 加上 %6的结果赋值给%7
- 将%7 加上 3 的结果赋值给%8
- 返回%8
IR 优化
在上面的IR代码中,可以看到,通过一点一点翻译语法树,生成的IR代码,看起来有点蠢,其实是可以优化的。
IR优化等级从低到高分别是: -O0 -O1 -O2 -O3 -Os -Ofast -Oz
可以使用命令进行优化:
clang -Os -S -fobjc-arc -emit-llvm main.m -o main.ll
也可以在 xcode 中设置:target -> build Setting -> Optimization Level
我们看一下Os级别优化后的代码:
上面IR代码的意思是:
- 将%0 加上 3 结果赋值给%3
- 将%3加上%1的结果赋值给%4
- 返回%4
优化后的IR代码,更加的简洁明了了!
bitcode
xcode7之后,如果开启了bitcode, 苹果会对IR文件做进一步的优化 生成.bc 文件的中间代码。
命令如下:
clang -emit-llvm -c main.ll -o main.bc
3:后端阶段->汇编文件(.s):
后端将接收到的 IR 结构化成不同的处理对象,并将其处理过程实现为一个个的Pass类型。通过处理 Pass ,来完成对IR的转换、分析和优化。然后生成汇编代码。
命令如下:
// 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
4:汇编阶段->生成目标文件(.o):
汇编器将汇编代码转换成机器码,生成一个个的目标文件的Mach-O文件
命令如下:
clang -fmodules -c main.s -o main.o
通过 nm 的命令,查看main.o的mach-O文件的符号
xcrun nm -nm main.o
打印结果如下:
可以看到执行命令时,报了一个错:没找到外部的 _printf 的符号。 因为这个函数是在外部引入的,这里需要把使用到的其他的库也 链接过来,才能不报错。
5:链接阶段-> 可执行的Mach-O文件:
将一个个的目标文件链接到一起,并且链接需要的动态库(.dylib)和静态库(.a) ,生成可执行文件 -(Mach-O)文件。
命令如下:
clang main.o -o main
可以看到打印结果中依然显示没有找到外部符号 printf , 但是后面多了(from libsystem)。指明_printf 所在的库是 libsystem。 这是因为libsystem动态库, 需要在运行时动态绑定 。 目前这个文件已经是一个正确的可执行文件了。
使用如下命令执行:
./main
执行结果:
6:绑定硬件架构:
根据x86_64硬件架构生成对应的可执行文件 (Mach-O)
总结编译流程
1. 各阶段使用的命令:
//// ====== 前端 开始=====
// 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
2. 各个阶段生成的文件类型:
3.编译流程图示:
3.2 OC 生成C++文件
- 功能: 可以把
OC
文件编译成C++
文件。 例如main.m
编译成main.cpp
文件,用来更好的查看代码的底层结构及实现逻辑,便于了解底层原理。 - 编译方式: 在终端中进入需要编译的文件所在目录,执行文件编译命令:
//1、将 main.m 编译成 main.cpp
clang -rewrite-objc main.m -o main.cpp
//2、将 ViewController.m 编译成 ViewController.cpp
clang -rewrite-objc -fobjc-arc -fobjc-runtime=ios-13.0.0 -isysroot / /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator13.7.sdk ViewController.m
//以下两种方式是通过指定架构模式的命令行,使用xcode工具 xcrun
//3、模拟器文件编译
- xcrun -sdk iphonesimulator clang -arch arm64 -rewrite-objc main.m -o main-arm64.cpp
//4、真机文件编译
- xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m -o main- arm64.cpp
举个🌰:
- (instancetype)init {
self = [super init];
if (self) {
NSLog(@"%@-----%@", [self class], [super class]);
}
return self;
}
编译后:
static instancetype _I_LGPerson_init(LGPerson * self, SEL _cmd) {
self = ((LGPerson *(*)(__rw_objc_super *, SEL))(void *)objc_msgSendSuper)((__rw_objc_super){(id)self, (id)class_getSuperclass(objc_getClass("LGPerson"))}, sel_registerName("init"));
if (self) {
NSLog((NSString *)&__NSConstantStringImpl__var_folders_86_0y_j3bzj65z6vw6hy1chw_4m0000gp_T_LGPerson_1615a9_mi_0, ((Class (*)(id, SEL))(void *)objc_msgSend)((id)self, sel_registerName("class")), ((Class (*)(__rw_objc_super *, SEL))(void *)objc_msgSendSuper)((__rw_objc_super){(id)self, (id)class_getSuperclass(objc_getClass("LGPerson"))}, sel_registerName("class")));
}
return self;
}
以上是小编给大家整理的编译资料,希望在以后的程序生涯中对你有所帮助。 青山不改,绿水长流,后会有期,感谢每一位佳人的支持!