了解 LLVM 编译器

LLVM概述

LLVM是架构编译器的框架系统,以C++编写而成,用于优化任意程序语言编写的程序的编译时间(compile-time)、链接时间(link-time)、运行时间(run-time)以及空闲时间(idle-time)。

LLVM的设计

LLVM的一大特色就是,有着独立的、完善的、严格约束的中间代码表示。这种中间代码,就是LLVM的字节码,是LLVM抽象的精髓,前端生成这种中间代码,后端自动进行各类优化分析,让用LLVM开发的编译器,都能用上最先见的后端优化技术。


传统编译器设计
  • Frontend

        编译器前端的任务是解析源代码(编译阶段),它会进行 词法分析、语法分析、语义分析、检查源代码是否存在错误,然后构建抽象语法树(Abstract Syntax Tree AST),LLVM的前端还会生成中间代码(intermediate representation,简称IR),可以理解为llvm是编译器 + 优化器, 接收的是IR中间代码,输出的还是IR,给后端,经过后端翻译成目标指令集

  • 优化器 Optimizer

        优化器负责进行各种优化,改善代码的运行时间,例如消除冗余计算等

  • 后端 Backend(代码生成器 Code Generator)

        将代码映射到目标指令集,生成机器代码,并且进行机器代码相关的代码优化。

LLVM将中间代码优化这个流程做到了极致,LLVM工具链,不但可以生成所支持的各个后端平台的代码,更可以方便的将各语言的前端编译后的模块链接到一起,你可以方便的在你的语言中调用C函数。

LLVM的设计

iOS的编译器架构

OCCC++ 使用的编译器前端是 Clang ,后端都是LLVM,如下图所示

iOS的编译器

Clang概述

Clang 是一个 C++ 编写、基于 LLVM、发布于 LLVM BSD 许可证下的 C/C++/Objective C/Objective C++ 编译器

Clang 编译流程

通过命令可以打印源码的编译阶段
clang -ccc-print-phases 源文件路径

得到结果如下:

  1. 输入文件:找到源文件
    input, "main.m", objective-c

  2. 预处理阶段:这个过程处理包括宏的替换,头文件的导入
    preprocessor, {0}, objective-c-cpp-output

  3. 编译阶段:进行词法分析、语法分析、检测语法是否正确,最终生成IR
    compiler, {1}, ir

  4. 后端:这里LLVM会通过一个一个的pass去优化,每个pass做一些事情,最终生成汇编代码
    backend, {2}, assembler

  5. 汇编代码生成目标文件
    assembler, {3}, object

  6. 链接:链接需要的动态库和静态库,生成可执行文件
    linker, {4}, image(镜像文件)

  7. 绑定:通过不同的架构,生成对应的可执行文件
    bind-arch, "x86_64", {5}, image

预处理阶段

    这个阶段主要是处理包括宏的替换,头文件的导入,可以执行命令 clang -E 源文件路径,执行完毕可以看到头文件的导入和宏的替换。define则在预处理阶段会被替换,所以经常被是用来进行代码混淆,目的是为了 app 安全,实现逻辑是:将 app 中核心类、核心方法等用系统相似的名称进行取别名,然后在预处理阶段就被替换,来达到代码混淆的目的。

clang -E main.m

编译阶段

词法分析

预处理完成后就会进行词法分析,这里会把代码切成一个个 token,比如大小括号、等于号、还有字符串等,

clang -fmodules -fsyntax-only -Xclang -dump-tokens 源文件路径
语法分析

语法分析,它的任务是验证语法是否正确,在词法分析的基础上将单词序列组合成各类此法短语,如程序、语句、表达式 等等,然后将所有节点组成抽象语法树(Abstract Syntax Tree AST),语法分析程序判断程序在结构上是否正确。

clang -fmodules -fsyntax-only -Xclang -ast-dump 源文件路径
  • FunctionDecl 函数
  • ParmVarDecl 参数
  • CallExpr 调用一个函数
  • BinaryOperator 运算符

生成中间代码IR

完成以上步骤后,就开始生成中间代码 IR 了,代码生成器(Code Generation)会将语法树自顶向下遍历逐步翻译成 LLVM IR,可以通过下面命令可以生成 .ll 的文本文件,查看 IR 代码。

clang -S -fobjc-arc -emit-llvm 源文件路径 -o 输出文件路径

Objective-C 代码在这一步会进行 runtime 桥接,property 合成、ARC 处理等。

IR 基本语法
  • @ 全局标识
  • % 局部标识
  • alloca 开辟空间
  • align 内存对齐
  • i32 32bit,4个字节
  • store 写入内存
  • load 读取数据
  • call 调用函数
  • ret 返回
Optimization Level

当然,IR 文件在 Objective-C 中是可以进行优化的,一般设置是在 target - Build Setting - Optimization Level(优化器等级)中设置。

Optimization Level 优化级别

LLVM 的优化级别分别是 -O0-O1 -O2 -O3 -Os,下面是带优化的生成中间代码 IR 的命令

clang -Os -S -fobjc-arc -emit-llvm 源文件路径 -o 输出文件路径
BitCode

xcode7 以后开启 bitcode ,苹果会做进一步优化,生成 .bc 的中间代码,我们通过优化后的 IR 代码生成 .bc代码

clang -emit-llvm -c .ll源文件 -o .bc文件

后端

LLVM 在后端主要是会通过一个个的 Pass 去优化,每个 Pass 做一些事情,最终生成汇编代码。我们通过最终的 .bc 或者 .ll 代码生成汇编代码:

 clang -S -fobjc-arc .bc 文件 -o .s 文件
 clang -S -fobjc-arc .ll 文件 -o .s 文件

生成汇编代码也可以进行优化

clang -Os -S -fobjc-arc .m 文件 -o .s 文件

生成目标文件

目标文件的生成,是汇编器以汇编代码作为插入,将汇编代码转换为机器代码,最后输出目标文件(object file)

clang -fmodules -c .s 文件 -o .o 文件

可以通过 nm 命令,查看下 main.o 中的符号

xcrun nm -nm .o 文件
  • undefined 表示在当前文件暂时找不到符号

  • external 表示这个符号是外部可以访问的

链接

链接主要是链接需要的动态库和静态库,生成可执行文件,其中静态库会和可执行文件合并,动态库是独立的。连接器把编译生成的 .o 文件和 .dyld.a 文件链接,生成一个 mach-o 文件

clang .o 文件 -o 输出文件

LLVM 工程编译

下载源码

git clone https://github.com/llvm/llvm-project.git

安装 cmake

brew install cmake

通过 xcode 编译 LLVM

  • cmake 编译成 Xcode 项目

    cd llvm-project
    mkdir build_xcode
    cd build_xcode
    cmake -G Xcode ../llvm
    
  • 打开 build_xcode/LLVM.xcodeproj 文件

  • 选择 Automatically Create Schemes (目前 xcode version 13.2.1)

  • 选择 ALL_BUILD Secheme 进行编译

通过 ninja 编译 LLVM

  • 安装 ninja

    brew install ninja
    
  • llvm-project 目录下创建文件夹 build_ninja 、 llvm-release

    cd llvm-project
    mkdir build_ninja
    mkdir llvm_release
    
  • cmake 编译

    cd build_ninja
    cmake -G Ninja ../llvm -DCMAKE_INSTALL_PREFIX=../llvm_release
    
  • ninja 运行

     ninja install
    
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,734评论 6 505
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,931评论 3 394
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,133评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,532评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,585评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,462评论 1 302
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,262评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,153评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,587评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,792评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,919评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,635评论 5 345
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,237评论 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,855评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,983评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,048评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,864评论 2 354

推荐阅读更多精彩内容