17-LLVM 的使用

195 阅读5分钟

本文已参与[新人创作礼]活动,一起开启掘金创作之路。

概述

llvm 是一种架构编译器(compiler)的框架系统,以c++编写而成,用于优化以任意程序语言编写的程序的编译时间、链接时间、运行时间以及空闲时间,兼容现有脚本。

传统的编译器设计
graph LR
SOURCE-CODE --> Frontend-Optimizer-Backend --> MACHINE-CODE
编译器前端

它的任务是解析源代码,进行词法分析、语法分析、语义分析、检查源代码是否存在错误,然后构建语法抽象树(ast),llvm 的前端还会生成中间代码 IR。

优化器

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

后端/代码生成器

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

iOS 的编译架构

Objective c/c/c++ 使用前端编译器都是clang,swift使用的是swift,后端都是llvm

graph LR
Clang --> llvm-Optimizer --> llvm-code-generator
Swift --> llvm-Optimizer

llvm 设计

llvm 设计的重要方面使用通用的代码表示形式 IR,它是用来在编译器中表现代码的形式,所以llvm可以为任何形式的语言编写前端,也可以为任意的硬件家都模式编写后端,

graph LR
c --> Clang-c/c++/oc-frontend --> llvm-Optimizer
fortian --> llvm-gcc-frontend --> llvm-Optimizer
haskell --> GHC-frontend --> llvm-Optimizer

llvm-Optimizer --> llvm-x86-backend --> x86
llvm-Optimizer --> llvm-PowerPC-backend --> PowerPC
llvm-Optimizer --> llvm-arm-backend --> arm

clang 是 LLVM中的一个子项目,他是基于llvm架构的轻量级编译器。

编译流程

通过以下命令可以打印源码的编译阶段

clang -ccc-print-phases main.m
❯ clang -ccc-print-phases main.m
               +- 0: input, "main.m", objective-c
            +- 1: preprocessor, {0}, objective-c-cpp-output
         +- 2: compiler, {1}, ir
      +- 3: backend, {2}, assembler
   +- 4: assembler, {3}, object
+- 5: linker, {4}, image
6: bind-arch, "x86_64", {5}, image
0:输入代码:找到源文件。
1:预编译处理:头文件的导入、宏定义的替换等。
2:编译阶段:进行词法分析,语法分析,检查语法是否正确,最终生成 IR。
3:后端:llvm会通过一个个pass去优化,每个pass做一些事情,最终生成汇编代码。
4:生成目标文件。
5:链接:链接所需要的动态库和静态库,生成可执行文件。
6:通过不同的架构,生成指定的可执行文件。
预处理阶段
clang -E main.m

执行以上命令,可以看到头文件的导入和宏的替换

编译阶段
词法分析
clang -fmodules -fsyntax-only -Xclang -dump-tokens main.m

执行以上命令,会进行词法分析,这里会把代码切成一个个token,比如括号字符串等号等。

❯ clang -fmodules       -fsyntax-only   -Xclang -dump-tokens    main.m
annot_module_include '#import <stdio.h>

#define A 10

int main(int argc, const char * argv[]) {
    print'		Loc=<main.m:8:1>
int 'int'	 [StartOfLine]	Loc=<main.m:12:1>
identifier 'main'	 [LeadingSpace]	Loc=<main.m:12:5>
l_paren '('		Loc=<main.m:12:9>
int 'int'		Loc=<main.m:12:10>
identifier 'argc'	 [LeadingSpace]	Loc=<main.m:12:14>
comma ','		Loc=<main.m:12:18>
const 'const'	 [LeadingSpace]	Loc=<main.m:12:20>
char 'char'	 [LeadingSpace]	Loc=<main.m:12:26>
star '*'	 [LeadingSpace]	Loc=<main.m:12:31>
identifier 'argv'	 [LeadingSpace]	Loc=<main.m:12:33>

语法分析
clang -fmodules -fsyntax-only -Xclang -ast-dump main.m

执行以上代码进行语法分析,检测语法是否报错,在词法分析的基础上把单词进行组合排列形成各种语法短语,如语句,程序等。然后将所有节点组成抽象语法树(AST)。语法分析判断源程序在结构上是否正确。

❯ clang -fmodules       -fsyntax-only   -Xclang -ast-dump       main.m
TranslationUnitDecl 0x7fef8203d008 <<invalid sloc>> <invalid sloc> <undeserialized declarations>
|-TypedefDecl 0x7fef8207e958 <<invalid sloc>> <invalid sloc> implicit __builtin_va_list 'struct __va_list_tag [1]'
| `-ConstantArrayType 0x7fef8207e900 'struct __va_list_tag [1]' 1
|   `-RecordType 0x7fef8207e740 'struct __va_list_tag'
|     `-Record 0x7fef8207e6a0 '__va_list_tag'
|-ImportDecl 0x7fef8207f1d0 <main.m:8:1> col:1 implicit Darwin.C.stdio
`-FunctionDecl 0x7fef8207f4a0 <line:12:1, line:15:1> line:12:5 main 'int (int, const char **)'
  |-ParmVarDecl 0x7fef8207f228 <col:10, col:14> col:14 argc 'int'
  |-ParmVarDecl 0x7fef8207f350 <col:20, col:38> col:33 argv 'const char **':'const char **'
  `-CompoundStmt 0x7fef85018e28 <col:41, line:15:1>
    |-CallExpr 0x7fef85018d98 <line:13:5, col:23> 'int'
    | |-ImplicitCastExpr 0x7fef85018d80 <col:5> 'int (*)(const char *, ...)' <FunctionToPointerDecay>
    | | `-DeclRefExpr 0x7fef85018c60 <col:5> 'int (const char *, ...)' Function 0x7fef85018808 'printf' 'int (const char *, ...)'
    | |-ImplicitCastExpr 0x7fef85018de0 <col:12> 'const char *' <NoOp>
    | | `-ImplicitCastExpr 0x7fef85018dc8 <col:12> 'char *' <ArrayToPointerDecay>
    | |   `-StringLiteral 0x7fef85018cb8 <col:12> 'char [3]' lvalue "%d"
    | `-BinaryOperator 0x7fef85018d18 <line:10:11, line:13:21> 'int' '+'
    |   |-IntegerLiteral 0x7fef85018cd8 <line:10:11> 'int' 10
    |   `-IntegerLiteral 0x7fef85018cf8 <line:13:21> 'int' 20
    `-ReturnStmt 0x7fef85018e18 <line:14:5, col:12>
      `-IntegerLiteral 0x7fef85018df8 <col:12> 'int' 0
生成中间代码IR

完成上面步骤可以生成中间代码

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

objective c 代码会在这一步进行runtime 桥接,property的合成ARC处理等等。

IR 的基本语法

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

IR 的优化

LLVM 优化级别是 -O0 -O1 -O2 -Os

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

bitcode

xocde7 之后,苹果对代码开启进一步优化,生成 .bc中间代码,我们通过优化后的IR代码生成.bc代码

clang -emit-llvm -c main.ll -o main.bc
生成汇编代码

我们通过.ll 或者 .bc 代码生成汇编代码

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

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

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

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

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

通过命令查看生成的目标文件中的符号

❯ xcrun nm      -nm     main.o
                 (undefined) external _printf
0000000000000000 (__TEXT,__text) external _main
_printf是一个 undefined external
undefined 表示在当前文件找不到符号_printf
external 表示这个符号是外部可以访问

生成的可执行文件(链接) 通过链接器把编译生成的.o文件和.dylib .a 文件,生成一个可执行的match-o文件

clang main.o -o main

通过命令查看链接之后的符号

❯ xcrun nm      -nm     main
                 (undefined) external _printf (from libSystem)
                 (undefined) external dyld_stub_binder (from libSystem)
0000000100000000 (__TEXT,__text) [referenced dynamically] external __mh_execute_header
0000000100003f77 (__TEXT,__text) external _main
0000000100008008 (__DATA,__data) non-external __dyld_private