[llvm cookbook] 1、LLVM设计与使用

1、LLVM设计与使用

了解如何把C语言代码编译为LLVM IR(Intermediate representation)及其他多种形式。

1.1 模块化设计

与其他编译器(如 GNU Compiler Collection) 不同,LLVM 设计目标是成为一系列的库。

让我们先准备一段测试代码作为优化器输入:

define i32 @test1(i32 %A) {
    %B = add i32 %A, 0
    ret i32 %B
}
define internal i32 @test(i32 %X, i32 %dead) {
    ret i32 %X
}
define i32 @caller() {
    %A = call i32 @test(i32 123, i32 456)
    ret i32 %A
}
  • 试试指令合并优化:sudo opt -S -instcombine testfile.ll -o output1.ll
    在这里插入图片描述

  • 试试参数消除优化:sudo opt -S -deadargelim testfile.ll -o output2.ll
    在这里插入图片描述

  • 原理:

    • 不同的优化 pass 整体风格一致
    • 每个 pass 编译成 .o 后链接得到一个库
    • pass 之间的依赖关系由 LLVM pass管理器 管理
      [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XIsoZpc0-1655738865598)(…/picture/pass-rela.png)]

更多:
codeGen 也采用模块设计理念,将代码分解为多个独立pass:

指令选择、寄存器分配、指令调度、代码布局优化、代码发射等

IR的跟多信息参阅 我翻译的IR参考原文

1.2 交叉编译Clang/LLVM

编译二进制文件的机器叫主机(host),运行生成二进制的平台叫目标平台(target). 为相同平台编译代码我们称为本机编译(native assembler), 不同称为 交叉编译(cross-compiler).

环境依赖略过,记录一下编译命令:

cmake -G Ninja<LLVM源码目录> \
-DCMAKE_CROSSCOMPILING=True \
-DCMAKE_INSTALL_PREFIX=<工具链安装目录(可选)> \
-DLLVM_TABLEGEN=<已安装的LLVM工具链目录 >/llvm-tblgen \
-DCLANG_TABLEGEN=<已安装的LLVM工具链目录 >/clang-tblgen \
-DLLVM_DEFAULT_TARGET_TRIPLE=arm-linux-gnueabihf \
-DLLVM_TARGET_ARCH=ARM \
-DLLVM_TARGETS_TO_BUILD=ARM \
-DCMAKE_CXX_FLAGS='-target armv7a-linux-gnueabihf -mcpu=cortex-a9
-I/usr/arm-linux-gnueabihf/include/c++/4.x.x/arm-linux-gnueabihf/
-I/usr/arm-linux-gnueabihf/include/ -mfloat-abi=hard -ccc-gcc-name
arm-linux-gnueabihf-gcc'

使用了 DCMAKE_INSTALL_PREFIX 会在 install-dir 创建 sysroot

注:ARM 平台的库还是得自己下载一份或自己编译构建.


1.3 将C源码转换为LLVM汇编码

sudo clang -emit-llvm -S multiply.c -o multiply.ll
sudo clang -cc1 -emit-llvm testfile.c -o testfile.ll    # 通过 cc1 生成
  • 原理:
    • 从词法分析开始(源码分解为token流:标识符、字面量、运算符)
    • token 流传递非词法分析器:在语言的 CFG(context free grammar) 指导下组织成 AST(抽象语法树)
    • 语义分析(检查正确性)
    • 生成 IR


1.4 将LLVM IR转换为 bitcode

bitcode 也叫字节码:

  • 位流 (bitstream)
  • 编码格式
define i32 @mult(i32 %a, i32 %b) #0 {
  %1 = mul nsw i32 %a, %b
  ret i32 %1
}
  • sudo llvm-as test.ll -o test.bc
    • 生成如下二进制(hexdump -C test.bc)
    Offset: 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F 	
    00000000: 42 43 C0 DE 35 14 00 00 05 00 00 00 62 0C 30 24    BC@^5.......b.0$
    00000010: 4D 59 BE 66 7D FB B4 4F 1B C8 24 44 01 32 05 00    MY>f}{4O.H$D.2..
    00000020: 21 0C 00 00 02 01 00 00 0B 02 21 00 02 00 00 00    !.........!.....
    00000030: 16 00 00 00 07 81 23 91 41 C8 04 49 06 10 32 39    ......#.AH.I..29
    ......
    
  • 原理:llvm-as 就是 LLVM 的汇编器,将 LLVM IR 转为 bitcode, 类似把汇编转为可执行文件
    • 为了做到这点,引入了 区块(block)记录(record) 的概念
      • 区块: 标识位流的区域,如函数体,符号表, 每个区块内容对应一个特定ID, 如函数ID是12
      • 记录:由记录码和一个整数值组成,描述了指令、全局变量描述符、类型描述中的实体
    • 可参考 官网描述

1.5 将LLVM bitcode转换为目标平台汇编码

  • 可通过 llc 或 clang 前端 获得:
    sudo llc test.bc -o test.s    # 默认消除帧指针
    sudo clang -S test.bc -o test.s -fomit-frame-pointer # 消除帧指针
    
  • 原理:llc把LLVM输入编译为特定架构的汇编语言,如不指定默认生成本机汇编码
    • 生成可执行文件需使用汇编器和链接器
    • -march=architechture 可生成特定架构的汇编码
    • -mcpu=cpu 可指定 cpu

1.6 将LLVM bitcode转回为LLVM汇编码

sudo llvm-dis test.bc -o test.ll

  • 原理:
    • llvm-dis 是反汇编器,省略文件名时会从标准输入读取

1.7 转换LLVM IR

  • 执行转换pass
opt -passname input.ll -o output.ll
clang -emit-llvm -S multiply.c -o multiply.ll # 得到未进行优化的输出
sudo opt -mem2reg -S multiply.ll -o multiply1.ll # 优化内存访问,从局部变量提升到寄存器
  • 原理:opt 作为 LLVM 的优化和分析工具,可使用多个 pass
    • adce 无用代码 消除
    • bb-vectorize 基本快向量化
    • constprop 简单常量传播
    • dce 无用代码消除
    • deadargelim 无用参数消除
    • globaldce 无用全局变量消除
    • globalopt 全局变量优化
    • gvn 全局变量编号
    • inline 函数内联
    • instcombine 冗余指令合并
    • licm 循环常量代码外提
    • loop-unswitch 循环外提
    • loweratomic 原子内建函数 lowering
    • lowerinvoke invode 指令lowering
    • mem2reg 内存访问优化 -->可帮助理解C指令映射IR指令
    • memcpyopt Memcpy优化
    • simplifycfg 简化 CFG
    • sink 代码提升
    • tailcallelim 尾调用消除

1.8 链接LLVM bitcode

  • 准备测试文件
    int func(int a) {
    a = a*2;
    return a;
    }
    
    #include<stdio.h>
    extern int func(int a);
    int main() {
    int num = 5;
    num = func(num);
    printf("number is %d\n", num);
    return num;
    }
    
    sudo clang -emit-llvm -S test1.c -o test1.ll
    sudo clang -emit-llvm -S test2.c -o test2.ll
    sudo llvm-as test1.ll -o test1.bc
    sudo llvm-as test2.ll -o test2.bc
    sudo llvm-link test1.bc test2.bc -o output.bc
    
  • 原理:与传统链接器一致,会解析这个文件中引用的符号。不同的是,不会生成一个二进制文件,只会链接 bitcode 文件

1.9 执行LLVM bitcode

lli output.bc

  • 这会执行 output.bc 文件。输出显示在标准输出
  • 原理:使用即时编译器(JIT)执行,不存在则用解释器执行

1.10 使用C语言前端——Clang

clang作为编译器驱动,可得到可执行文件,使用 -E 可做预处理器,可用下面的命令获得抽象语法树

clang -cc1 test.c -ast-dump # -cc1 保证只使用前端,而不是驱动器

可用 -S 生成汇编码

clang test.c -S -emit-llvm -o -
  • 原理:clang 可作为预处理器、编译器驱动、前端以及代码驱动器,因此取决于你指定的参数
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值