iOS开发:LLVM及Clang插件初探

一、LLVM概述

LLVM是架构编译器(Compiler)的框架系统,以C++编写而成,用于优化以任意程序语言编写的程序的编译时间(compile-timne)、链接时间(link-time)、运行时间(run-time)以及空闲时间(idle-time),对开发者保持开放,并兼容已有脚本。
目前LLVM已经被苹果iOS开发工具、Xilinx Vivado、Facebook、Google等各大公司采用。

传统编译器设计包括编译器前端、优化器、后端:
  • 编译器前端(Frontend):编译器前端的任务时解析源代码。他会进行词法分析、语法分析、语义分析、检查源代码是否存在错误,然后构建抽象语法树(Abstract Syntax Tree, AST)。LLVM的前端还会生成中间代码(Intermediate Representation, IR)。
  • 优化器(Optimizer):优化器负责进行各种优化。改善代码的运行时间,例如消除冗余的计算等。
  • 后端(Backend)/代码生成器(Code Generator):将代码映射到目标指令集。生成机器语言,并且进行机器语言相关的代码优化。

iOS的编译器架构

LLVM的设计

LLVM使用通用的代码表示形式(IR),他是用来在编译器中表示代码的形式,所以LLVM可以位任意编程语言独立编写前端,摒弃可以位任意硬件架构独立编写后端。

Clang

Clang时LLVM项目中的一个子项目。他是基于LLVM架构的轻量级编译器,负责C、C++、Objective语言的编译,属于LLVM架构中的编译器前端。

二、编译流程

案例代码包括一个math-add.h文件,内部定义一个add函数。另一个是explorer.m文件,内部引入math-add.h,定义一个宏,然后func中做测试。

//math-add.h
int add(int x, int y){
    return x + y;
}
//explorer.m
#include "math-add.h"
#define KCapacity 200
int func(void){
    int a = KCapacity;
    int b = 256;
    int c = add(a, b);
    return c;
}

打印编译阶段

clang -ccc-print-phases main.m

打印结果

//0:输入文件:找到源文件
0: input, "main.m", objective-c 
//1:预处理阶段:处理宏的替换、头文件的导入等
1: preprocessor, {0}, objective-c-cpp-output
//2:编译阶段:进行此法分析、语法分析、检查语法是否正确,生成IR
2: compiler, {1}, ir
//3:后端:LLVM会通过一个一个的Pass去优化,每个Pass做一些事情,最终生成汇编代码
3: backend, {2}, assembler
//4:生成目标文件
4: assembler, {3}, object
//5:链接:链接需要的动态库和静态哭,生成客执行文件。
5: linker, {4}, image
//6:通过不同的架构生成对应的可执行文件
6: bind-arch, "x86_64", {5}, image
1.预处理阶段
  • 执行命令clang -E explorer.m,结果如下图,可以看到,宏定义直接被替换掉了,导入了头文件中的函数。
2.编译阶段
  • 词法分析:执行命令clang -fmodules -fsyntax-only -Xclang -dump-tokens explorer.m,结果会把代码切成一个个的Token。这里可以看到在intfunc(){int等,这种粒度上进行了拆分。

  • 语法分析:执行命令clang -fmodules -fsyntax-only -Xclang -ast-dump explorer.mFunctionDecl是函数的声明,标记在explorer.m的第6行第一个字符开始,到11行第1个字符结束,函数名称为func,返回值为int,参数列表为void。CompoundStmt复合语句,这里表示函数体从第5行第13个字符开始到第7行第一个字符结束。VarDecl表示变量声明。

3.生成汇编代码
  • 执行命令:clang -S -fobjc-arc -emit-llvm explorer.m可以生成LLVM
    IR文件,结果如下图:
    %1 = alloca i32, align 4:表示开辟一个(int32类型)32位大小的内存空间,4字节对齐,返回值放在%1.
    store i32 200, i32* %1, align 4:表示将int32类型的200,存储到%1。
    %4 = load i32, i32* %1, align 4:表示将%1中的值加载到内存,放在%5。
    %6 = call i32 @add(i32 %4, i32 %5):表示调用add函数,参数是%4和%5的值,返回值存储在%6.
    ret i32 %7:表示return%7中的值。

    IR代码的生成还可以设置优化级别:分别是-O0-O1-O2-O3-Os,使用clang -O1 -S -fobjc-arc -emit-llvm explorer.m.
    如果开启bitcode,会进一步优化,从.ll到.bc中间代码:clang -emit-llvm -c explorer.ll -o explorer.bc.
    通过.ll或者.bc生成汇编代码:clang -S -fobjc-arc explorer.ll -o explorer.sclang -S -fobjc-arc explorer.bc -o explorer.s
4.生成目标文件(汇编器生成.o文件)

目标文件的生成,是汇编器以汇编代码作为输入、将汇编代码转换为机器代码,最后输出目标文件object file。命令clang -fmodules -c explorer.s -o explorer.o

5.生成可执行文件(链接,生成mach-o文件)

连接器把编译产生的.o文件和库(.dylib和.a文件)生成一个mach-o文件。指令clang explorer.o -o explorer
这里没有需要找到main函数才能执行成功。我们将最初的源码中的func改成main即可。然后走一遍生成.ll->生成.s->生成.o再执行如上代码即可成功生成可执行文件。
查看链接之后的符号命令:xcrun nm -nm explorer,结果如下,可以看到我们定义的函数。

二、LLVM的编译和Clang插件的编写

LLVM项目下载
//LLVM项目下载:
git clone https://mirrors.tuna.tinghua.edu.cn/git/llvm/llvm.git

//在llvm/tools下下载Clang:
cd llvm/tools
git clone https://mirrors.tuna.tsinghua.edu.cn/git/llvm/clang.git

//在llvm.projects目录下下载compiler-rt,libcxx, libcxxabi
cd ../projects
git clone https://mirrors.tuna.tsinghua.edu.cn/git/llvm/compiler-rt.g it
git clone https://mirrors.tuna.tsinghua.edu.cn/git/llvm/libcxx.git
git clone https://mirrors.tuna.tsinghua.edu.cn/git/llvm/libcxxabi.git

//在Clang下安装extra工具
cd ../tools/clang/tools
git clone https://mirrors.tuna.tsinghua.edu.cn/git/llvm/clang-tools-e xtra.git
LLVM编译
//通过brew 安装 cmake
brew install cmake

//在llvm目录同级创建一个xcode项目目录,比如build-xcode,然后进入到这个文件夹执行命令即可
mkdir build-code
cd build-code
came -G Xcode ../llvm

编译到最后如果没有error,就是构建xcode项目成功了。

创建插件
  • 创建插件目录:在/llvm/tools/clang/tools目录下创建文件夹如NXChecker
  • 修改/llvm/tools/clang/tools目录下的CMakeLists.txt文件,末尾新增add_clang_subdirectory(NXChecker)
  • NXChecker目录下新建一个NXChecker.cpp的文件和CMakeLists.txt文件。在CMakeLists.txt中添加如下代码
    add_llvm_library(NXChecker MODULE BUILDTREE_ONLY NXChecker.cpp)
  • 进入build-xcode重新生成一下Xcode项目,cmake -G Xcode ../llvm
  • 最后就可以在LLVMXcode项目中看到Loadable modules目录下有自己的Plugin目录了,所有的插件代码将在NXChecker中完成编写。
编写插件

(略)
编写完成插件后,运行一下,切换到当前插件的Target-NXChecker对应的scheme-NXChecker,然后运营一下,会发现/build-xcode/Debug/lib/NXChecker.dylib文件,这个就是我们插件的可执行文件。

测试插件

~/Desktop/LLVM/build_xcode/Debug/bin/clang -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator.sdk/ -Xclang -load -Xclang ~/Desktop/LLVM/build_xcode/Debug/lib/NXChecker.dylib -Xclang -add-plugin -Xclang NXChecker -c ~/Workspace/Microapp/iOSApp-Objective-C/iOSApp-Objective-C/ViewController.m

  • clang文件的路径:~/Desktop/LLVM/build_xcode/Debug/bin/clang
  • SDK路径:/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator.sdk/
  • 插件路径:~/Desktop/LLVM/build_xcode/Debug/lib/NXChecker.dylib
  • 测试源码文件路径:~/Workspace/Microapp/iOSApp-Objective-C/iOSApp-Objective-C/ViewController.m
Xcode集成插件
  • Build Settings->Other C Flags中添加如下内容:-Xclang -load -Xclang ~/Desktop/LLVM/build_xcode/Debug/lib/NXChecker.dylib -Xclang -add-plugin -Xclang NXChecker
  • 新增用户自定义设置 Add User-Defined Setting
  • 在User-Defined中新增 CC:~/Desktop/LLVM/build-xcode/Debug/bin/clang;CXX:~/Desktop/LLVM/build-xcode/Debug/bin/clang++
  • 将Build Settings中 Enable Index-While-Building FunctionalityDefault改成NO

你可能感兴趣的:(iOS开发:LLVM及Clang插件初探)