02-LLVM IR 基础(持续更新)

LLVM IR官方文档:https://llvm.org/docs/Reference.html#llvm-ir

本文只是学习并记录笔记,如有错误或不足请谅解指正,谢谢!


IR概述

LLVM IR(Intermediate Representation)是LLVM的代码表示,是用于在编译器中表示代码的形式。旨在轻量级和低级,同时具有表现力、类型和可扩展性。旨在成为某种“通用 IR”,通过处于足够低的级别,可以将高级想法清晰地映射到它。

LLVM 是一种基于静态单一分配 (SSA) 的表示。

LLVM IR有三种形式:

①内存中的编译中间表示

②磁盘上的二进制码 bitcode(例如.bc文件)

③可读汇编文本(例如.ll文件)

这三种形式之间是等价的。

IR结构:

IR结构

  • Module,可以被视为一个.c文件的 IR 表示,每个 Module都是相对独立的东西,主要包含了声明或者定义的函数、声明或定义的全局变量、当前 Module的基本信息
  • Function,是 Module中以List的方式存放的。函数内的第一个基本块叫做入口基本块。无法单独存在,必须是在某个 Module里,包含了大量 BasicBlock 、参数和返回值类型、可变参数列表、函数的attribute和其他函数的基本信息
  • BasicBlock,是 Function 中以List方式存放。包含了大量的 Instruction ,前驱、后继的 BasicBlock,以及一些基本信息,每个 BasicBlock都可以视为一段顺序执行的代码
  • Instruction,是 BasicBlock 中以List方式存放的,LLVM IR中的最小可执行单位,每一条指令都单占一行

IR头部:

LLVM IR头部是一些Target Information

; ModuleID = 'hello_llvm.c'
source_filename = "hello_llvm.c"
target datalayout = "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128"
target triple = "x86_64-unknown-linux-gnu"

信息为:

  • ModuleID:编译器用于区分不同模块的ID
  • source_filename:源文件名
  • target datalayout:目标机器架构数据布局
    • e:内存存储模式为小端模式
    • m:o:目标文件的格式是Mach格式
    • i64:64:64位整数的对齐方式是64位,即8字节对齐
    • f80:128:80位扩展精度浮点数的对齐方式是128位,即16字节对齐
    • n8:16:32:64:整型数据有8位的、16位的、32位的和64位的
    • S128:128位栈自然对齐
  • target triple:用于描述目标机器信息的一个元组,一般形式是<architecture>-<vendor>-<system>[-extra-info]

IR语法

标识符

LLVM 标识符有两种基本类型:全局和本地

  • 全局标识符(函数、全局变量)以'@'字符作为开头前缀
  • 本地标识符(寄存器名称、类型)以'%'字符作为开头前缀

此外标识符还有三种不同形式,用于不同的目的:

  1. 已命名的值:表示带有前缀的字符串。例如%foo@DivisionByZero
  2. 未命名的值:表示带有前缀的无符号数值。例如:%12@2%44
  3. 常量
LLVM 要求值以前缀开头有两个原因:编译器不需要担心名称与保留字的冲突,并且保留字集将来可能会扩展而不会受到惩罚。此外,未命名的标识符允许编译器快速提出临时变量,而不必避免符号表冲突

LLVM代码示例:#将整数变量”%X”乘以 8

1、使用mul乘

%result = mul i32 %X, 8

2、使用shl移位

%result = shl i32 %X, 3

3、使用add加

%0 = add i32 %X, %X           ; yields i32:%0
%1 = add i32 %0, %0 ; yields i32:%1
%result = add i32 %1, %1

最后一种方法说明了LLVM的几个词汇特征:

  • 注释用 ‘ ;’ 分隔
  • 当计算结果未分配给命名值时,需要创建未命名的临时对象
  • 未命名的临时对象按顺序编号(递增计数器,从 ’0’ 开始)

高层结构

  • 模块结构: LLVM 程序由Module's 组成,每个 Module's 都是输入程序的转换单元。每个模块由函数、全局变量和符号表组成。多个模块可以被LLVM 链接器(linker)组合在一起。 通常,一个模块由一个全局值列表组成(其中函数和全局变量都是全局值),全局值由指向内存位置的指针
  • 链接类型:
    • private:该类型的全局变量只能被当前模块内的对象直接访问。private的值不会出现在目标代码的符号表中。
    • internal:与private类似,不过它的值会作为局部变量出现在目标文件中。类似C语言中static的概念。
    • available_externally:该类型的变量不会被输出到与 LLVM 模块对应的目标文件中。对于链接者来说,该类型相当于一个外部声明。这种类型只允许在定义时使用,而不能在声明时使用。
    • linkonce:链接时,这种类型的变量会和其他同名变量merge。可用于实现某些形式的内联函数、模板或其他代码。
    • 官网还有其他许多类型,这里不一一翻译介绍
  • 调用约定:
    • ccc:C调用约定。如果没有指定其他类型,那么就默认是这种类型
    • fastcc:快速调用约定。使生成的目标代码尽可能快
    • 其他调用约定参见官网

部分常见参数

  • align <n>:这表明指针值或指针向量具有指定的对齐方式

部分常见指令

  • alloca

    • 语法:
      <result> = alloca [inalloca] <type> [, <ty> <NumElements>] [, align <alignment>] [, addrspace(<num>)]     ; yields type addrspace(num)*:result
    • 功能:指令在当前执行函数的栈帧上分配内存,当这个函数返回给它的调用者时自动释放
    %ptr = alloca i32                             ; yields i32*:ptr
    %ptr = alloca i32, i32 4 ; yields i32*:ptr
    %ptr = alloca i32, i32 4, align 1024 ; yields i32*:ptr
    %ptr = alloca i32, align 1024 ; yields i32*:ptr
  • store

    • 功能:写入内存
    %ptr = alloca i32                               ; yields i32*:ptr
    **store i32 3, i32* %ptr** ; yields void
    %val = load i32, i32* %ptr ; yields i32:val = i32 3
  • load

    • 功能:从内存中读取
    %ptr = alloca i32                               ; yields i32*:ptr
    store i32 3, i32* %ptr ; yields void
    **%val = load i32, i32* %ptr** ; yields i32:val = i32 3
  • getelementptr

    • 功能:获取聚合数据结构的子元素的地址。它只执行地址计算,不访问内存

    使用方法看文档感觉较多,主要知道是获取元素地址

    %1 = getelementptr i64* %0, i32 0
  • ptrtoint…to

    • 语法:
      <result> = ptrtoint <ty> <value> to <ty2>             ; yields ty2
    • 功能:“ptrtoint”指令将指针或指针向量值转换为整数(或整数向量)类型 ty2。
    • 参数:“ptrtoint”指令需要一个值进行强制转换,该值必须是指针类型的值或指针向量,以及将其强制转换为 ty2 的类型,该类型必须是整数或整数类型的向量。
    %2 = ptrtoint i64* %1 to i64
  • inttoptr...to

    • 语法:
      <result> = inttoptr <ty> <value> to <ty2>[, !dereferenceable !<deref_bytes_node>][, !dereferenceable_or_null !<deref_bytes_node>]             ; yields ty2
    • 功能:“inttoptr”指令将整数值转换为指针类型 ty2。
    • 参数:’ inttoptr’ 指令需要一个整数值来转换,以及一个类型来转换它,它必须是一个指针 类型
    %4 = inttoptr i64 %3 to i64*
文章作者: HotSpurzzZ
文章链接: http://example.com/2022/05/07/02-LLVM IR 基础/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 HotSpurzzZ