(Xcode) 編譯器小白筆記 - LLVM前端Clang
(Xcode) 編譯器小白筆記 - LLVM前端Clang
本文为笔记型式呈现,并非全部原创,来源见文末
Compiler
Clang - LLVM
Apple(包括中后期的NeXT) 一直使用GCC作为官方的编译器。GCC作为开源世界的编译器标准一直做得不错,但Apple对编译工具会提出更高的要求。
Clang这个软体专案在2005年由苹果电脑发起,是LLVM编译器工具集的前端(front-end),目的是输出程式码对应的抽象语法树(Abstract Syntax Tree, AST),并将程式码编译成LLVM Bitcode。接着在后端(back-end)使用LLVM编译成平台相关的机器语言 。
先看结果
main.m
1 |
|
直接编译成执行档
clang -fmodules main.m
产出 .out (executable)
Clang (Frontend前端)
是一个C、C++、Objective-C和Objective-C++程式语言的编译器前端
Clang源码结构
Clang步骤
clang -ccc-print-phases main.m
1.Input (Driver)
指定语言 , 架构, 输入file
clang -x objective-c main.m
2.Preprocessor(预处理)
import 头文件, include头文件等 ,macro宏展开,处理’#’开头指令
单做预处理, 并取得预处理结果
clang -E main.m
预处理最终结果:
1 | # 1 "/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.14.sdk/System/Library/Frameworks/Foundation.framework/Headers/FoundationLegacySwiftCompatibility.h" 1 3 |
依据上面的结果能明显看到 header被换成了明确的全局位置,常量DEFINEEight
也被替换进代码里,讲到import 就不得不提 modules
2.1 Modules 模块 (-fmodules)
文章里提及:
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 <stdio.h>
,类似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。
3 Lexical Analysis (词法分析 Lex, Tokenization) -> .i (Tokens)
此步骤是Compiler里的基本程序,将字符一个一个的读进Lexer里,并根据构词规则识别 Token(单词),此处还不会校验语法
做词法分析并把Token分析结果展示出来
clang -fmodules -fsyntax-only -Xclang -dump-tokens main.m
每一个标记都包含了对应的源码内容和其在源码中的位置。注意这里的位置是宏展开之前的位置,这样一来,如果编译过程中遇到什么问题,clang 能够在源码中指出出错的具体位置。
-fsyntax-only
: Run the preprocessor, parser and type checking stages.
4 语法分析(Semantic Analysis) -> AST
语法分析,在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
5 抽象语法树 Abstract Syntax Tree
可以说是Clang的核心,大部分的优化, 判断都在AST处理(例如寻找Class, 替换代码…等)
此步骤会将 Clang Attr 转换成 AST 上的 AttributeList,能在clang插件上透过 Decl::getAttr<T>
获取
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(类型)
子类过于详细不在这多写
6 代码生成 CodeGen -> IR中间代码(.ll)
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。
Extra: Clang 插件
使用 libclan g, clang, LibTooling 插件
可以改变 clang 生成代码的方式,增加更强的类型检查,或者按照自己的定义进行代码的检查分析等等。要想达成以上的目标,
reference:
Clang插件
了解Clang-ast
Understanding the Clang AST
AST Detail
ClangAST
https://clang.llvm.org/
# 深入剖析-iOS-编译-Clang
https://llvm.org/devmtg/2017-06/2-Hal-Finkel-LLVM-2017.pdf
# 从Swift桥接文件到Clang-LLVM