iOS LLVM及编译过程

2,375 阅读4分钟

LLVM概述

  • LLVM 是以 C++ 编写的架构编译器(compiler)的框架系统
  • 从以下方面优化任意语言编写的的程序
    • 编译时间 compile time
    • 链接时间 link time
    • 运行时间 run time
    • 空闲时间 idle time

传统编译器设计

编译器前端 Frontend

编译器前端的任务是 解析源代码

  • 检查代码正确性
    • 词法分析
    • 语法分析
    • 语义分析
  • 构建抽象语法树
  • LLVM的前端还会生成 中间代码intermediate representationIR

优化器 Optimizer

优化器端的任务是改善代码的运行时间,例如消除冗余运算等。

编译器后端 Backend / 代码生成器CodeGenerator

  • 将代码映射到目标指令集
  • 生成机器语言
  • 机器相关的代码优化

iOS的编译器架构

OCcc++ 使用的编译器前端是clangSwift使用的是Swift;后端都是LLVM

LLVM的设计

传统的编译器设计把前端、优化器、后端集成在一起,需要支持新语言或新硬件结构时就必须重新开发一套编译器。LLVM的设计模式很好的解决了这种不便。

  • LLVM的编译器前端会生成通用的中间代码intermediate respresentation IR
  • 优化器对中间代码处理不需要改变
  • 需要支持新语言时只需要单独开发前端
  • 需要支持新架构时只需要单独编写后端

Clang

  • LLVM中的一个子项目
  • 基于LLVM的轻量级编译器
  • 替代GCC,提供更快的编译速度
  • 负责编译OCCC++
  • LLVM架构的编译器前端

编译流程

打印编译流程

clang -ccc-print-phases main.m 

  • 0、输入文件:找到源文件
  • 1、预处理:包括替换宏和导入头文件
  • 2、编译阶段:词法分析、语法分析、语义分析,最终生成IR
  • 3、后端:通过一个个Pass去优化,每个Pass完成一部分功能,最终生成汇编代码
  • 4、生成目标文件
  • 5、链接:链接需要的动态库和静态库,生成目标文件
  • 6、通过不同架构,生成对应的可执行文件

预处理

clang -E main.m

执行后可以看到头文件导入以及宏被替换。

词法分析

预处理后会进行词法分析,词法分析会把代码切片成一个个token

clang -fmodules -fsyntax-only -Xclang -dump-tokens main.m

执行后可以看到每个词(token)所在的位置

语法分析

语法分析会验证语法是否正确,在词法分析的基础上把单词组合成各种语法短语,然后把节点组成抽象的语法树(Abstract Syntax Tree,AST)

clang -fmodules	-fsyntax-only -Xclang -ast-dump	main.m

如果找不到头文件,可以手动导入SDK

clang -isysroot SDK路径 -fmodules -fsyntax-only	-Xclang	-ast-dump main.m

生成中间代码IR(intermediate representation)

处理完上述步骤,代码生成器(Core Generator)会根据语法树自顶向下生成LLVM IR。OC会在这里进行runtime的桥接:property的合成、ARC处理等。

clang -S -fobjc-arc -emit-llvm main.m

LLVM的优化级别有 -O0 -O1 -O2 -O3 -Os

clang -Os -S -fobjc-arc -emit-llvm main.m

执行指令可以得到.ll文件

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

查看未优化的main.ll

优化后的main.ll

bitCode

xcode7以后开启了bitCode,苹果对代码做了进一步优化并生成.bc文件。通过.ll文件生成.bc文件。

clang -emit-llvm -c main.ll -o main.bc

生成汇编代码

可以通过.bc.ll文件生成汇编代码

clang -S -fobjc-arc main.bc -o main.s
clang -S -fobjc-arc main.ll -o main.s

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

clang -Os -S -fobjc-arc main.bc -o main.s

生成目标文件

输入汇编代码,转化成机器代码,最后输出目标文件。

clang -fmodules -c main.s -o main.o

查看main.o中的符号

xcrun nm -nm main.o

可以看到print函数、test函数、main函数

  • _printf 可以看到 undefined extranal 标示
  • undefined 代表它在当前文件找不到
  • extranal 代表它可以外部访问

生成可以执行文件

把编译的.o文件和dylib.a文件连接,生成mach-o文件

clang	main.o	-o	main

执行可执行文件,可以看见输出了正确结果