LLVM 之 IR 篇(1):零基础快速入门 LLVM IR

Author: stormQ

Created: Tuesday, 20. July 2021 10:43PM

Last Modified: Monday, 26. July 2021 09:55PM



摘要

本文介绍了 LLVM IR 的基本概念、操纵命令以及一些常见的语法。从而,初步了解 LLVM IR 以便更深入地研究相关内容。


LLVM IR 概述

LLVM IR(Intermediate Representation)是一种中间语言表示,作为编译器前端和后端的分水岭。LLVM 编译器的前端——Clang 负责产生 IR,而其后端负责消费 IR。

编译器 IR 的设计体现了权衡的计算思维。低级的 IR(即更接近目标代码的 IR)允许编译器更容易地生成针对特定硬件的优化代码,但不利于支持多目标代码的生成。高级的 IR 允许优化器更容易地提取源代码的意图,但不利于编译器根据不同的硬件特性进行代码优化。

LLVM IR 的设计采用common IRspecific IR相结合的方式。common IR旨在不同的后端共享对源程序的相同理解,以将其转换为不同的目标代码。除此之外,也为多个后端之间共享一组与目标无关的优化提供了可能性。specific IR允许不同的后端在不同的较低级别优化目标代码。这样做,既可以支持多目标代码的生成,也兼顾了目标代码的执行效率。

LLVM IR 有如下 3 种等价形式:

注:这里的汇编文件不是通常所说的汇编语言文件,而是 LLVM 位码文件的可读表示。

LLVM IR 的特点如下:


LLVM IR 的相关命令

LLVM IR 的相关命令如下:

命令格式 作用 示例
clang <source-file or assembly-file> -emit-llvm -c -o <output-file> 生成 LLVM IR 的位码文件
  • clang test.c -emit-llvm -c -o test.bc
  • clang test.ll -emit-llvm -c -o test.bc
  • clang <source-file or bitcode-file> -emit-llvm -S -c -o <output-file> 生成 LLVM IR 的汇编文件
  • clang test.c -emit-llvm -S -c -o test.ll
  • clang test.bc -emit-llvm -S -c -o test.ll
  • llvm-as <assembly-file> -o <output-file> 生成 LLVM IR 的位码文件 llvm-as test.ll -o test.bc
    llvm-dis <assembly-file> -o <output-file> 生成 LLVM IR 的汇编文件 llvm-dis test.bc -o test.ll
    llvm-extract -func=foo <assembly-file or bitcode-file> -o <output-file> 提取指定的函数到位码文件
  • llvm-extract -func=foo test.ll -o test-fn.bc
  • llvm-extract -func=foo test.bc -o test-fn.bc

  • LLVM IR 的语法


    Source Filename

    source_filename描述了源文件的名称及其所在路径。

    语法:

    source_filename = "/path/to/source.c"

    示例 1:

    source_filename = "test.c"

    注:上述内容是通过运行clang test.c -emit-llvm -S -c -o test.ll命令得到的。

    示例 2:

    source_filename = "../test.c"

    注:上述内容是通过运行clang ../test.c -emit-llvm -S -c -o test.ll命令得到的。

    从上面两个示例的结果可以看出,source_filename的值就是包含clang 输入的源文件(原封不动)的字符串。

    注:相关的官方文档—— Source Filename

    返回上一级


    Data Layout

    target datalayout描述了目标机器中数据的内存布局方式,包括:字节序、类型大小以及对齐方式。

    语法:

    target datalayout = "layout specification"

    示例(in Ubuntu 20.04):

    target datalayout = "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128"

    注:

    需要注意的是, <abi>对齐方式指定了类型所需的最小对齐方式,而<pref>对齐方式指定了一个可能更大的值。<pref>可以省略,省略时其值等于<abi>的值。

    注:相关的官方文档——Data Layout

    返回上一级


    Target Triple

    target triple描述了目标机器是什么,从而指示后端生成相应的目标代码。

    注:可以通过命令选项-mtriple覆盖该信息。

    语法(典型的):

    target triple = "ARCHITECTURE-VENDOR-OPERATING_SYSTEM"

    target triple = "ARCHITECTURE-VENDOR-OPERATING_SYSTEM-ENVIRONMENT"

    示例(in Ubuntu 20.04):

    target triple = "x86_64-unknown-linux-gnu"

    注:上述内容表示目标机器的指令集是x86_64,供应商未知,操作系统是linux,环境是GNU

    注:相关的官方文档—— Target Triple

    返回上一级


    Identifiers

    LLVM IR 中的标识符分为:全局标识符和局部标识符。全局标识符以@开头,比如:全局函数、全局变量。局部标识符以%开头,类似于汇编语言中的寄存器。

    标识符有如下 3 种形式:

    注:相关的官方文档—— Identifiers

    返回上一级


    Functions

    define用于定义一个函数。

    语法:

    define [linkage] [PreemptionSpecifier] [visibility] [DLLStorageClass]
           [cconv] [ret attrs]
           <ResultType> @<FunctionName> ([argument list])
           [(unnamed_addr|local_unnamed_addr)] [AddrSpace] [fn Attrs]
           [section "name"] [comdat [($name)]] [align N] [gc] [prefix Constant]
           [prologue Constant] [personality Constant] (!name !N)* { ... }

    示例(in Ubuntu 20.04):

    define dso_local void @foo(i32 %x) #0 {
      ; 省略 ...
    }

    注:

    attributes #0 = { noinline nounwind optnone uwtable "disable-tail-calls"="false" "frame-pointer"="all" "less-precise-fpmad"="false" "min-legal-vector-width"="0" "no-infs-fp-math"="false" "no-jump-tables"="false" "no-nans-fp-math"="false" "no-signed-zeros-fp-math"="false" "no-trapping-math"="true" "stack-protector-buffer-size"="8" "target-cpu"="x86-64" "target-features"="+cx8,+fxsr,+mmx,+sse,+sse2,+x87" "tune-cpu"="generic" "unsafe-fp-math"="false" "use-soft-float"="false" }

    LLVM IR 中,函数体是由基本块(Basic Blocks)构成的。基本块是由一系列顺序执行的语句构成的,并(可选地)以标签作为起始。不同的标签代表不同的基本块。

    基本块的特点如下:

    一个完整的函数实现如下:

      7 define weak dso_local void @foo(i32 %x) #0 {
      8 entry:
      9   %x.addr = alloca i32, align 4
     10   %y = alloca i32, align 4
     11   %z = alloca i32, align 4
     12   store i32 %x, i32* %x.addr, align 4
     13   %0 = load i32, i32* %x.addr, align 4
     14   %cmp = icmp eq i32 %0, 0
     15   br i1 %cmp, label %if.then, label %if.end
     16 
     17 if.then:                                          ; preds = %entry
     18   store i32 5, i32* %y, align 4
     19   br label %if.end
     20 
     21 if.end:                                           ; preds = %if.then, %entry
     22   %1 = load i32, i32* %x.addr, align 4
     23   %tobool = icmp ne i32 %1, 0
     24   br i1 %tobool, label %if.end2, label %if.then1
     25 
     26 if.then1:                                         ; preds = %if.end
     27   store i32 6, i32* %z, align 4
     28   br label %if.end2
     29 
     30 if.end2:                                          ; preds = %if.then1, %if.end
     31   ret void
     32 }

    注:

    对应的源码如下:

    void foo(int x) {
      int y, z;
      if (x == 0)
        y = 5;
      if (!x)
        z = 6;
    }

    注:相关的官方文档——FunctionsAttribute GroupsRuntime Preemption Specifiers‘alloca’ Instruction‘store’ Instruction‘load’ Instruction‘icmp’ Instruction‘br’ Instruction‘ret’ Instruction

    返回上一级


    Function Attributes

    常见的函数属性如下:

    属性名称 作用
    noinline 表示在任何情况下都不能将函数视为内联函数进行处理
    nounwind 表示函数从不引发异常(如果抛出了异常,则为运行期未定义行为)
    optnone 表示大多数优化过程将跳过此函数
    uwtable 该选项常见于 ELF x86-64 abi,具体作用?
    mustprogress

    注:相关的官方文档——Function Attributes[IR] Adds mustprogress as a LLVM IR attribute

    返回上一级


    Metadata

    注:相关的官方文档——Metadata

    返回上一级


    llvm.loop

    llvm.loop是元数据(Metadata)之一,用于为循环附加一些属性。这些属性将传递给优化器和代码生成器。

    示例:

    for.inc:                                          ; preds = %if.end
      %6 = load i32, i32* %i, align 4
      %inc = add nsw i32 %6, 1
      store i32 %inc, i32* %i, align 4
      br label %for.cond, !llvm.loop !2

    ; 省略 ...

    !2 = distinct !{!2, !3}
    !3 = !{!"llvm.loop.mustprogress"}

    所有的元数据都以英文叹号!开头。!llvm.loop !2,表示循环元数据节点为!2,它是一系列其他元数据的集合,集合中的每项都表示循环的一个属性。上述示例中,该集合为!2 = distinct !{!2, !3}。由于历史遗留原因,该集合的第一项必须是对自身的引用。上述示例中,该循环元数据节点携带的属性只有llvm.loop.mustprogress。该属性表示:如果循环不与外部发生交互(可以理解为:移除该循环后也不影响程序的行为),那么循环可能会被移除。

    注:相关的官方文档——‘llvm.loop’‘llvm.loop.mustprogress’ Metadata

    返回上一级


    Module Flags Metadata

    注:相关的官方文档——Module Flags Metadata

    返回上一级


    llvm.module.flags

    llvm.module.flags是命名元数据(Named Metadata)之一,用于描述模块级别的信息。

    llvm.module.flags是一个包含一系列元数据节点的集合。集合中的每个元数据节点都是一个形如{<behavior>, <key>, <value>}的三元组。其中,<behavior>用于指定来自不同模块的<key><value>都相同的元数据合并时如何处理,<key>表示元数据在本模块中的唯一标识符(仅在本模块内唯一,来自不同模块的元数据可能有相同的标识符),<value>表示元数据所携带信息的值。

    常见的<behavior>值及行为如下:

    behavior 值 行为
    1 如果两个值不一致,则发出错误,否则生成的值就是操作数的值

    示例:

    !llvm.module.flags = !{!0}

    ; 省略 ...

    !0 = !{i32 1, !"wchar_size", i32 4}

    上述示例中,!llvm.module.flags = !{!0}表示集合中只有一个元数据节点!0!0 = !{i32 1, !"wchar_size", i32 4}表示标识符为"wchar_size"的元数据所携带信息的值在所有模块中都应该是 4,否则会报错。

    返回上一级


    llvm.ident

    llvm.ident,用于表示 Clang 的版本信息。

    示例:

    !llvm.ident = !{!1}

    ; 省略 ...

    !1 = !{!"clang version 10.0.0-4ubuntu1 "}

    返回上一级


    References


    下一篇:LLVM 之 IR 篇(2):如何编写生成 LLVM IR 的工具

    上一篇:上一级目录

    首页