美文网首页
了解 LLVM 编译器

了解 LLVM 编译器

作者: _涼城 | 来源:发表于2022-03-02 09:52 被阅读0次

    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
      

    相关文章

      网友评论

          本文标题:了解 LLVM 编译器

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