美文网首页iOS开发进阶
iOS开发进阶:LLVM及Clang插件初探

iOS开发进阶:LLVM及Clang插件初探

作者: __Null | 来源:发表于2021-12-24 23:31 被阅读0次

    一、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插件初探

        本文链接:https://www.haomeiwen.com/subject/ufgrqrtx.html