《iOS底层原理文章汇总》
LLVM是架构编译器(compiler)的框架系统,以C++缩写而成,用于优化以任意程序语言编写的程序的编译时间(compile-time)、链接时间(link-time)、运行时间(run-time)以及空闲时间(idle-time),对开发者保持开放,并兼容已有脚本。
什么是编译器
.c文件和.py文件的编译差别
解释器语言:一边去读去解释一边让CPU去执行
编译器语言:让编译器先去编译生成一个可执行文件如MachO文件(a.out),这个文件是二进制组合文件010101,虽然helloDemo.py和helloDemo.c也是二进制数据010101的组合,但CPU无法直接执行,a.out的二进制数据010101组合,CPU能直接运行的指令。
二进制数据010101的组合分为两种,一种是数据,数据读了之后给不同的应用程序去解析,如helloDemo.py和helloDemo.c,一种是指令,cpu读了直接分析直接执行,指令和数据没有区分
编译器的作用是将高级语言代码编译成CPU(硬件)能看的懂的010101组合,也叫机器语言,这些010101的组合就叫做指令
Xcode中Cmd+B在Product文件夹下面会生成XXX.app文件,显示包内容里面可以看到XXX的二进制可执行文件
函数调用栈
函数申请一段栈空间,往栈中开始压栈,压完数据后还要出栈,给栈平衡,函数调用完成后,告诉编译器要出栈,寻址跳转,开辟内存空间,给内存空间输入值,再执行相应代码,将栈平衡,再跳回去
test(){
func1();
func();
func2();
}
func() -- 申请一段栈空间。 (16字节内存)
{
int a = 10;//4字节
int b = 10;//4字节
}
- 高级语言的出现让程序更简洁,可读性更好,但是程序运行更复杂了
LLVM概述
Clang
Clang是如何编译源代码的
clang -ccc-print-phases main.m
静态库会和MachO文件进行合并,动态库是独立的,执行的时候才链接绑定
- 静态库和动态库的区别
静态库会和MachO文件进行合并,动态库是独立的,执行的时候才链接绑定
预处理阶段
clang -E main.m 执行完毕后可以看到头文件的导入和宏的替换
编译阶段
语法分析
语法错误的情况
生成中间代码
#import
int test(int a,int b){
return a + b + 3;
}
int main(int argc, const char * argv[]) {
int a = test(1, 2);
printf("%d",a);
return 0;
}
IR代码
查看生成的test代码里面有重复的地方,没有做任何优化的情况下,生成的IR代码有55行
; Function Attrs: noinline nounwind optnone ssp uwtable
define i32 @test(i32, i32) #0 { int a0 int a1
%3 = alloca i32, align 4 int a3
%4 = alloca i32, align 4 int a4
store i32 %0, i32* %3, align 4 a3 = a0
store i32 %1, i32* %4, align 4 a4 = a1
%5 = load i32, i32* %3, align 4 a5 = a3
%6 = load i32, i32* %4, align 4 a6 = a4
%7 = add nsw i32 %5, %6 a7 = a5 + a6
%8 = add nsw i32 %7, 3 a8 = a7 + 3
ret i32 %8
}
在Build Settings中Code generation中有优化器设置,Optimization Level里面有None,Fast,Faster,Fastest,Fastest Smallest,Fastest Aggressive Optimization,Smallest Aggressive Size Optimization,选择后再次进行编译的时候,能进行相应等级的优化
对main.m进行-Qs等级的优化,生成IR代码main2.ll如下,代码行数变为42行,test和main函数代码明显变少
clang -Os -S -fobjc-arc -emit-llvm main.m -o main2.ll
; Function Attrs: norecurse nounwind optsize readnone ssp uwtable
define i32 @test(i32, i32) local_unnamed_addr #0 {
%3 = add i32 %0, 3
%4 = add i32 %3, %1
ret i32 %4
}
bitCode
IR源代码main.ll生成中间代码main.bc文件
clang -emit-llvm -c main.ll -o main.bc
main.m main.ll main.bc三种代码都能生成汇编代码,下面三种都是没有优化的,生成的汇编代码行数一致。在生成汇编代码的时候不会进行优化了,优化的结果是在生成IR中间代码的时候
clang -S -fobjc-arc main.bc -o main.s
clang -S -fobjc-arc main.ll -o main1.s
clang -S -fobjc-arc main.m -o main2.s
生成汇编代码.s的时候也可以增加优化参数clang -Os -S -fobjc-arc main.m -o main3.s,此时生成的汇编代码只有48行了
前面将.m文件用优化器-Os生成得到main2.ll文件,main3.s是直接用main.m通过优化参数-Os等级优化后的汇编代码,现将main2.ll文件通过优化等级-Os生成汇编代码main4.s,clang -Os -S -fobjc-arc main2.ll -o main4.s
查看两者的大小区别,发现一模一样,说明优化只有一次,无论前端给参数还是后端给参数,只是经过不断不断的PASS,一个又一个的llvm的PASS来进行优化的
生成目标文件
一个MachO文件是多个.o文件的集合,clang -fmodules -c main3.s -o main.o
生成main.o文件,main3.s是汇编assembler source text文件,main.o是MachO文件,优化过后和优化之前的目标文件大小是不一样的
函数中printf是外部符号,既然有外部符号,那就需要链接,链接后,运行时才能找到对应的动态库。静态库直接进行拷贝合并。静态、动态指的是 链接的过程。
【静态库】的特点如下:
1.静态库对函数库的链接是在编译期完成的。执行期间代码装载速度快。
2.使可执行文件变大,浪费空间和资源(占空间)。
3.对程序的更新、部署与发布不方便,需要全量更新。如果 某一个静态库更新了,所有使用它的应用程序都需要重新编译、发布给用户。
【动态库】在程序编译时并不会链接到目标代码中,而是在运行时才被载入。不同的应用程序如果调用相同的库,那么在内存中只需要有一份该共享库的实例,避免了空间浪费问题。同时也解决了静态库对程序的更新的依赖,用户只需更新动态库即可。
【动态库】在内存中只存在一份拷贝,如果某一进程需要用到动态库,只需在运行时动态载入即可。
【动态库】的特点:
1.动态库把对一些库函数的链接载入推迟到程序运行时期(占时间)。
2.可以实现进程之间的资源共享。(因此动态库也称为共享库)
3.将一些程序升级变得简单,不需要重新编译,属于增量更新。