(Xcode) 編譯器小白筆記 - LLVM前端Clang

2,979 阅读5分钟

本文为笔记型式呈现,并非全部原创,来源见文末

Compiler

Three-Phase编译器架构

Clang - LLVM

Apple(包括中后期的NeXT) 一直使用GCC作为官方的编译器。GCC作为开源世界的编译器标准一直做得不错,但Apple对编译工具会提出更高的要求。

Clang这个软体专案在2005年由苹果电脑发起,是LLVM编译器工具集的前端(front-end),目的是输出程式码对应的抽象语法树(Abstract Syntax Tree, AST),并将程式码编译成LLVM Bitcode。接着在后端(back-end)使用LLVM编译成平台相关的机器语言 。

MDCC2016的 session

先看结果

main.m

#import <Foundation/Foundation.h>
#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)

clang

Clang (Frontend前端)

是一个C、C++、Objective-C和Objective-C++程式语言的编译器前端

Clang源码结构

source:https://llvm.org/devmtg/2017-06/2-Hal-Finkel-LLVM-2017.pdf

Clang步骤

clang -ccc-print-phases main.m

Clang-LLVM步骤

source: https://blog.csdn.net/u014795020/article/details/72514109

1.Input (Driver)

指定语言 , 架构, 输入file

clang -x objective-c main.m

2.Preprocessor(预处理)

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 <stdio.h>,类似java的package,目前Clang

  1. #include的机制是 编译器会去递回检查每个Header,header inculde的 header 文章里提了几个弱点: Compile-time scalability:耗时编译 Fragility:多引入顺序或导致宏冲突 Conventional workarounds:C语言长久的息惯,导致代码较丑 Tool confusion

  2. 然而编译器在碰到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 能够在源码中指出出错的具体位置。

展示Token(部分)

-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

AST

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(类型) 子类过于详细不在这多写

source: https://blog.csdn.net/u014795020/article/details/72514109

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。

project Build Setting

http://clang.llvm.org/docs/CommandGuide/clang.html#code-generation-options

Extra: Clang 插件

使用 libclan g, clang, LibTooling 插件

可以改变 clang 生成代码的方式,增加更强的类型检查,或者按照自己的定义进行代码的检查分析等等。要想达成以上的目标,

reference:

Clang插件 了解Clang-ast Understanding the Clang AST AST Detail ClangAST clang.llvm.org/ # 深入剖析-iOS-编译-Clang llvm.org/devmtg/2017… # 从Swift桥接文件到Clang-LLVM