记录一次clang如何一步一步的编译的。
写一个最基本的Hello world,然后保存位a.m。
#import <Foundation/Foundation.h> int main(int argc, char *argv[]) { @autoreleasepool { NSLog(@"Hello world!"); } }
在命令行输入
clang -ccc-print-phases a.m
可以看到编译源文件需要的几个不同的阶段
0: input, "a.m", objective-c 1: preprocessor, {0}, objective-c-cpp-output 2: compiler, {1}, ir 3: backend, {2}, assembler 4: assembler, {3}, object 5: linker, {4}, image 6: bind-arch, "x86_64", {5}, image
这样能够了解到过程和重要的信息。 查看oc的c实现可以使用如下命令
clang -rewrite-objc a.m
查看操作内部命令,可以使用 -### 命令
clang -### a.m -o main
想看清clang的全部过程,可以先通过-E查看clang在预处理处理这步做了什么。
clang -E a.m
执行完后可以看到文件
# 1 "/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.12.sdk/System/Library/Frameworks/Foundation.framework/Headers/FoundationLegacySwiftCompatibility.h" 1 3 # 185 "/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.12.sdk/System/Library/Frameworks/Foundation.framework/Headers/Foundation.h" 2 3 # 2 "a.m" 2 int main(){ @autoreleasepool { NSLog(@"Hello"); } }
这个过程的处理包括宏的替换,头文件的导入,以及类似#if的处理。预处理完成后就会进行词法分析,这里会把代码切成一个个 Token,比如大小括号,等于号还有字符串等。
clang -fmodules -fsyntax-only -Xclang -dump-tokens a.m
然后是语法分析,验证语法是否正确,然后将所有节点组成抽象语法树 AST 。
clang -fmodules -fsyntax-only -Xclang -ast-dump a.m
完成这些步骤后就可以开始IR中间代码的生成了,CodeGen 会负责将语法树自顶向下遍历逐步翻译成 LLVM IR,IR 是编译过程的前端的输出后端的输入。
clang -S -fobjc-arc -emit-llvm a.m -o main.ll
这里 LLVM 会去做些优化工作,在 Xcode 的编译设置里也可以设置优化级别-01,-03,-0s,还可以写些自己的 Pass,官方有比较完整的 Pass 教程: Writing an LLVM Pass — LLVM 5 documentation 。
clang -O3 -S -fobjc-arc -emit-llvm a.m -o main.ll
Pass 是 LLVM 优化工作的一个节点,一个节点做些事,一起加起来就构成了 LLVM 完整的优化和转化。
如果开启了 bitcode 苹果会做进一步的优化,有新的后端架构还是可以用这份优化过的 bitcode 去生成。
clang -emit-llvm -c a.m -o main.bc
生成汇编
clang -S -fobjc-arc a.m -o main.s
生成目标文件
clang -fmodules -c a.m -o main.o
生成可执行文件,这样就能够执行看到输出结果
clang main.o -o main
执行
./a Hello world!
参考资料:
https://www.objc.io/issues/6-build-tools/compiler/
https://github.com/ming1016/study/wiki/%E6%B7%B1%E5%85%A5%E5%89%96%E6%9E%90-iOS-%E7%BC%96%E8%AF%91-Clang—LLVM