转自:https://juejin.im/post/6844903716709990414 做笔记之用
本文为笔记型式呈现,并非全部原创,来源见文末
Apple(包括中后期的NeXT) 一直使用GCC作为官方的编译器。GCC作为开源世界的编译器标准一直做得不错,但Apple对编译工具会提出更高的要求。
Clang这个软体专案在2005年由苹果电脑发起,是LLVM编译器工具集的前端(front-end),目的是输出程式码对应的抽象语法树(Abstract Syntax Tree, AST),并将程式码编译成LLVM Bitcode。接着在后端(back-end)使用LLVM编译成平台相关的机器语言 。
main.m
#import
#define DEFINEEight 8
int main(){
@autoreleasepool {
int eight = DEFINEEight;
int six = 6;
NSString* site = [[NSString alloc] initWithUTF8String:"starming"];
int rank = eight + six;
NSLog(@"%@ rank %d", site, rank);
}
return 0;
}
复制代码
直接编译成执行档
clang -fmodules main.m
产出 .out (executable)
是一个C、C++、Objective-C和Objective-C++程式语言的编译器前端
clang -ccc-print-phases main.m
指定语言 , 架构, 输入file
clang -x objective-c main.m
import 头文件, include头文件等 ,macro宏展开,处理'#'开头指令
单做预处理, 并取得预处理结果
clang -E main.m
预处理最终结果:
# 1 "/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.14.sdk/System/Library/Frameworks/Foundation.framework/Headers/FoundationLegacySwiftCompatibility.h" 1 3
# 185 "/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.14.sdk/System/Library/Frameworks/Foundation.framework/Headers/Foundation.h" 2 3
# 2 "main.m" 2
int main(){
@autoreleasepool {
int eight = 8;
int six = 6;
NSString* site = [[NSString alloc] initWithUTF8String:"starming"];
int rank = eight + six;
NSLog(@"%@ rank %d", site, rank);
}
return 0;
}
复制代码
依据上面的结果能明显看到 header被换成了明确的全局位置,常量DEFINEEight
也被替换进代码里,讲到import 就不得不提 modules
2.1 Modules 模块 (-fmodules)
参考 LLVM modules文档
文章里提及: Modules provide an alternative, simpler way to use software libraries that provides better compile-time scalability and eliminates many of the problems inherent to using the C preprocessor to access the API of a library.
Clang 以简单的 import std.io
概念取代 原本冗余的函数库(libraries)引进 #include
,类似java的package,目前Clang
#include的机制是 编译器会去递回检查每个Header,header inculde的 header 文章里提了几个弱点: Compile-time scalability:耗时编译 Fragility:多引入顺序或导致宏冲突 Conventional workarounds:C语言长久的息惯,导致代码较丑 Tool confusion
然而编译器在碰到import时,会直接载入module对应的二进制文件并取得他的api,一个module不依赖外部header,只编译一次,api也只解析一次, 当然module也有些缺点包括 namesspace(可能重名), 改库代码, 无法适应各种机器的Arch。
此步骤是Compiler里的基本程序,将字符一个一个的读进Lexer里,并根据构词规则识别 Token(单词),此处还不会校验语法
做词法分析并把Token分析结果展示出来
clang -fmodules -fsyntax-only -Xclang -dump-tokens main.m
每一个标记都包含了对应的源码内容和其在源码中的位置。注意这里的位置是宏展开之前的位置,这样一来,如果编译过程中遇到什么问题,clang 能够在源码中指出出错的具体位置。
-fsyntax-only
: Run the preprocessor, parser and type checking stages.
语法分析,在Clang中有Parser和Sema两个模块配合完成,验证语法是否正确,并给出正确的提示。
4.1 Parser
遍历每个Token做词句分析,生成一个 节点(Nodes)该有的资讯
4.2 Semantic
在Lex 跟 syntax Analysis之后, 也就是在这个阶段已经确保 词 句 语法已经是正确的形式了,semantic 接着做return values, size boundaries, uninitialized variables 等检查,之后根据当前的资讯,生成语意节点(Nodes),并将所有节点组合成抽象语法书(AST)
做 语法分析 并展示 AST
clang -fmodules -fsyntax-only -Xclang -ast-dump main.m
可以说是Clang的核心,大部分的优化, 判断都在AST处理(例如寻找Class, 替换代码...等)
此步骤会将 Clang Attr 转换成 AST 上的 AttributeList,能在clang插件上透过 Decl::getAttr
获取
Clang Attributes 是 Clang 提供的一种源码注解,方便开发者向编译器表达某种要求,参与控制如 Static Analyzer、Name Mangling、Code Generation 等过程, 一般以
__attribute__(xxx)
的形式出现在代码中, Ex: NS_CLASS_AVAILABLE_IOS(9_0)
结构跟其他Compiler的AST相同与其他编译器不同的是 Clang的AST是由C++构成类似Class,Variable的层级表示,其他的则是以汇编语言编写。
这代表着AST也能有对应的api,这让AST操作, 获取信息 都比较容易,甚至还夹带着地址跟代码位置。
AST Context: 存储所有AST相关资讯, 且提供ASTMatcher等遍历方法
Node三大Class
Decl - Declarations(声明), Stmt - Statements(陈述句), type(类型)
子类过于详细不在这多写
CodeGen负责将语法树从顶至下遍历,翻译成LLVM IR,是LLVM Backend 的输入,是前后端的桥接语言。
产出IR: clang -S -fobjc-arc -emit-llvm main.m -o main.ll
LLVM IR 有三种表示格式,第一种是 bitcode 这样的存储格式,以 .bc 做后缀,第二种是可读的以 .ll,第三种是用于开发时操作 LLVM IR 的内存格式。
产出Bit clang -emit-llvm -c main.m -o main.bc
查看BitCode llvm-dis < main.bc | less
6.1 IR 优化 Optimization
IR提供了多种优化选项,-01 -02 -03 -0s.... 对应着不同的入参,有比如类似死代码清理,内联化,表达式重组,循环变量移动这样的 Pass。
可以改变 clang 生成代码的方式,增加更强的类型检查,或者按照自己的定义进行代码的检查分析等等。要想达成以上的目标,
reference:
Clang插件 了解Clang-ast Understanding the Clang AST AST Detail ClangAST clang.llvm.org/ # 深入剖析-iOS-编译-Clang llvm.org/devmtg/2017… # 从Swift桥接文件到Clang-LLVM