LLVM 简介与安装

2023/02/18 LLVM 共 8838 字,约 26 分钟

Gitee 地址

https://gitee.com/mirrors/LLVM

https://gitee.com/mirrors/LLVM.git

Github 地址

https://github.com/llvm/llvm-project

https://github.com/llvm/llvm-project.git

官网

https://llvm.org/


LLVM简介

LLVM 是一个编译器工具链, 采用三段式设计, 包含以下三部分:

  • 前端(Frontend): 进行词法分析、语法分析, 生成抽象语法树, 生成中间语言 IR (Intermediate Representation);
  • 优化器(Optimizer): 分析中间语言,避免多余的计算,提高性能;
  • 后端(Backend): 根据中间语言,生成对应的 CPU 架构指令;

未命名绘图.drawio (3)

同时, LLVM 包含了以下多个子项目:

项目名称描述
LLVM Core包含一个源代码和目标架构无关的独立配置器,一个针对很多主流(甚至于一些非主流)的CPU的汇编代码生成支持。这些核心库围绕IR来构建。
Clang一个C/C++/Objective-C编译器,提供高效快速的编译效率,风格良好、极其有用的错误和警告信息。
LLDB基于LLVM提供的库和Clang构建的优秀的本地调试器。原生支持调试多线程程序。
LLDclang/llvm内置的链接器
dragonegggcc插件,可将GCC的优化和代码生成器替换为LLVM的相应工具。
libc++, libc++ ABI符合标准的,高性能的C++标准库实现,以及对C++11的完整支持。
compiler-rt为动态测试工具(如AddressSanitizer,ThreadSanitizer,MemorySanitizer和DataFlowSanitizer)提供了运行时库的实现。为像“__fixunsdfdi”这样的低级代码生成器支持进程提供高层面的调整实现,也提供当目标没有用于实现核心IR操作的短序列本机指令时生成的其他调用。
OpenMP提供一个OpenMP运行时,用于Clang中的OpenMP实现。
vmkit基于LLVM的Java和.NET虚拟机实现。
polly支持高级别的循环和数据本地化优化支持的LLVM框架,使用多面体模型实现一组缓存局部优化以及自动并行和矢量化。
libclcOpenCL(开放运算语言)标准库的实现.
klee基于LLVM编译基础设施的符号化虚拟机。它使用一个定理证明器来尝试评估程序中的所有动态路径,以发现错误并证明函数的属性。 klee的一个主要特性是它可以在检测到错误时生成测试用例。
SAFECode用于C / C ++程序的内存安全编译器。 它通过运行时检查来检测代码,以便在运行时检测内存安全错误(例如,缓冲区溢出)。 它可用于保护软件免受安全攻击,也可用作Valgrind等内存安全错误调试工具

编译 LLVM 的过程非常缓慢且极度消耗内存, 在虚拟环境中估计需要 30G 以上的物理内存, 单核编译需要 2.5 h 以上. 如果内存不足, 可以创建虚拟内存 swap memory .

在 Ubuntu 20.04 中安装

sudo apt-get update
# LLVM 中 LLDB 需要 Python 3.6 以上版本
sudo apt-get install make build-essential cmake python3 -y
# 从 Gitee 克隆 13.0.1 分支到目录 llvmproject-13.0.1
git clone https://gitee.com/mirrors/LLVM.git -b llvmorg-13.0.1 llvmproject-13.0.1
# 创建编译目录
cd llvmproject-13.0.1 && mkdir build && cd build
# cmake 设置编译选项, 如果没有特殊需求强烈建议将 DCMAKE_BUILD_TYPE 参数设置为 Release, 速度会差几百倍!
cmake ../llvm -DCMAKE_BUILD_TYPE=Release -DLLVM_ENABLE_PROJECTS="clang;LLDB;LLD;compiler-rt" -DLLVM_ENABLE_RUNTIMES="libcxx;libcxxabi" -DCMAKE_INSTALL_PREFIX=${HOME}/.local/llvm/13.0.1/
# 编译相关文件
make -j
# 将文件安装到 DCMAKE_INSTALL_PREFIX 指定的目录
make install

Docker 环境验证

version: '3.7'
services:
  app:
    image: ubuntu:20.04
    container_name: LLVM-build
    command: >
      bash -c 'apt-get update \
               && apt-get install make build-essential cmake python3 -y \
               && tar -xvf LLVM.tar.gz \
               && cd LLVM \
               && mkdir build \
               && cd build \
               && cmake ../llvm -DCMAKE_BUILD_TYPE=Release -DLLVM_ENABLE_PROJECTS="clang;LLDB;LLD;compiler-rt" -DLLVM_ENABLE_RUNTIMES="libcxx;libcxxabi" -DCMAKE_INSTALL_PREFIX=${HOME}/.local/llvm/13.0.1/ \
               && make -j \
               && make install \
               && echo Finish!
               '
    volumes:
      - ./LLVM.tar.gz:/workspace/LLVM.tar.gz:ro
    working_dir: /workspace
    environment:
      - AUTHOR=DonaldTrump
      - DEBIAN_FRONTEND=noninteractive
      - TZ=Etc/UTC
    cpus: 96

其中 LLVM.tar.gz 文件为 13.0.1 版本的 git 仓库压缩包; 环境变量 DEBIAN_FRONTENDTZ 为使用 apt 安装 tzdata 包时所需的环境变量, 如果没有会进入交互环境导致 Docker 容器发生阻塞.

使用 docker-compose up 运行此容器.

同样, 可以使用 Dockerfile 构建 LLVM 容器, Dockerfile 内容如下:

FROM ubuntu:20.04

WORKDIR /workspace

ENV DEBIAN_FRONTEND=noninteractive
ENV TZ=Etc/UTC

WORKDIR /workspace

RUN    apt-get update \
    && apt-get install make build-essential cmake python3 git -y

RUN    git clone https://gitee.com/mirrors/LLVM.git -b llvmorg-13.0.1 llvmproject-13.0.1

RUN    cd llvmproject-13.0.1 && mkdir build && cd build \
    && cmake ../llvm -DCMAKE_BUILD_TYPE=Release -DLLVM_ENABLE_PROJECTS="clang;LLDB;LLD;compiler-rt" -DLLVM_ENABLE_RUNTIMES="libcxx;libcxxabi" \
    && make -j \
    && make install

CMD ["llc", "--version"]

使用 docker build --cpuset-cpus "1-90" . 命令多核构建此容器, 使用 docker tag <old_id> <new_id> 明明此容器

中间语言 IR

尝试从一个简单的例子理解 IR, 源码如下:

// filename: mian.c
#include <stdio.h>

size_t g = 0xabcdef;

int main() {
    int n = 0;
    printf("Hello");
    printf("World");
    for(int i=0; i<5; ++i) {
        n += i;
    }
    return n;
}

使用如下命令将 C 语言源码编译成 IR 文件(即 *.ll):

clang -g -emit-llvm -S main.c -o main.ll

输出的中间语言 IR 文件 main.ll 内容如下:

; ModuleID = 'main.c'
source_filename = "main.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"

@g = dso_local global i64 11259375, align 8, !dbg !0
@.str = private unnamed_addr constant [6 x i8] c"Hello\00", align 1
@.str.1 = private unnamed_addr constant [6 x i8] c"World\00", align 1

; Function Attrs: noinline nounwind optnone uwtable
define dso_local i32 @main() #0 !dbg !15 {
  %1 = alloca i32, align 4
  %2 = alloca i32, align 4
  %3 = alloca i32, align 4
  store i32 0, i32* %1, align 4
  call void @llvm.dbg.declare(metadata i32* %2, metadata !19, metadata !DIExpression()), !dbg !20
  store i32 0, i32* %2, align 4, !dbg !20
  %4 = call i32 (i8*, ...) @printf(i8* getelementptr inbounds ([6 x i8], [6 x i8]* @.str, i64 0, i64 0)), !dbg !21
  %5 = call i32 (i8*, ...) @printf(i8* getelementptr inbounds ([6 x i8], [6 x i8]* @.str.1, i64 0, i64 0)), !dbg !22
  call void @llvm.dbg.declare(metadata i32* %3, metadata !23, metadata !DIExpression()), !dbg !25
  store i32 0, i32* %3, align 4, !dbg !25
  br label %6, !dbg !26

6:                                                ; preds = %13, %0
  %7 = load i32, i32* %3, align 4, !dbg !27
  %8 = icmp slt i32 %7, 5, !dbg !29
  br i1 %8, label %9, label %16, !dbg !30

9:                                                ; preds = %6
  %10 = load i32, i32* %3, align 4, !dbg !31
  %11 = load i32, i32* %2, align 4, !dbg !33
  %12 = add nsw i32 %11, %10, !dbg !33
  store i32 %12, i32* %2, align 4, !dbg !33
  br label %13, !dbg !34

13:                                               ; preds = %9
  %14 = load i32, i32* %3, align 4, !dbg !35
  %15 = add nsw i32 %14, 1, !dbg !35
  store i32 %15, i32* %3, align 4, !dbg !35
  br label %6, !dbg !36, !llvm.loop !37

16:                                               ; preds = %6
  %17 = load i32, i32* %2, align 4, !dbg !40
  ret i32 %17, !dbg !41
}

; Function Attrs: nofree nosync nounwind readnone speculatable willreturn
declare void @llvm.dbg.declare(metadata, metadata, metadata) #1

declare dso_local i32 @printf(i8*, ...) #2

attributes #0 = { noinline nounwind optnone uwtable "frame-pointer"="all" "min-legal-vector-width"="0" "no-trapping-math"="true" "stack-protector-buffer-size"="8" "target-cpu"="x86-64" "target-features"="+cx8,+fxsr,+mmx,+sse,+sse2,+x87" "tune-cpu"="generic" }
attributes #1 = { nofree nosync nounwind readnone speculatable willreturn }
attributes #2 = { "frame-pointer"="all" "no-trapping-math"="true" "stack-protector-buffer-size"="8" "target-cpu"="x86-64" "target-features"="+cx8,+fxsr,+mmx,+sse,+sse2,+x87" "tune-cpu"="generic" }

!llvm.dbg.cu = !{!2}
!llvm.module.flags = !{!9, !10, !11, !12, !13}
!llvm.ident = !{!14}

!0 = !DIGlobalVariableExpression(var: !1, expr: !DIExpression())
!1 = distinct !DIGlobalVariable(name: "g", scope: !2, file: !3, line: 3, type: !6, isLocal: false, isDefinition: true)
!2 = distinct !DICompileUnit(language: DW_LANG_C99, file: !3, producer: "clang version 13.0.1 (https://gitee.com/mirrors/llvm-project 75e33f71c2dae584b13a7d1186ae0a038ba98838)", isOptimized: false, runtimeVersion: 0, emissionKind: FullDebug, enums: !4, globals: !5, splitDebugInlining: false, nameTableKind: None)
!3 = !DIFile(filename: "main.c", directory: "/home/lizhenghao/Workspace/llvm-chatgpt/Note_ENV/part-01")
!4 = !{}
!5 = !{!0}
!6 = !DIDerivedType(tag: DW_TAG_typedef, name: "size_t", file: !7, line: 46, baseType: !8)
!7 = !DIFile(filename: ".local/llvm/13.0.1/lib/clang/13.0.1/include/stddef.h", directory: "/home/lizhenghao")
!8 = !DIBasicType(name: "long unsigned int", size: 64, encoding: DW_ATE_unsigned)
!9 = !{i32 7, !"Dwarf Version", i32 4}
!10 = !{i32 2, !"Debug Info Version", i32 3}
!11 = !{i32 1, !"wchar_size", i32 4}
!12 = !{i32 7, !"uwtable", i32 1}
!13 = !{i32 7, !"frame-pointer", i32 2}
!14 = !{!"clang version 13.0.1 (https://gitee.com/mirrors/llvm-project 75e33f71c2dae584b13a7d1186ae0a038ba98838)"}
!15 = distinct !DISubprogram(name: "main", scope: !3, file: !3, line: 5, type: !16, scopeLine: 5, spFlags: DISPFlagDefinition, unit: !2, retainedNodes: !4)
!16 = !DISubroutineType(types: !17)
!17 = !{!18}
!18 = !DIBasicType(name: "int", size: 32, encoding: DW_ATE_signed)
!19 = !DILocalVariable(name: "n", scope: !15, file: !3, line: 6, type: !18)
!20 = !DILocation(line: 6, column: 9, scope: !15)
!21 = !DILocation(line: 7, column: 5, scope: !15)
!22 = !DILocation(line: 8, column: 5, scope: !15)
!23 = !DILocalVariable(name: "i", scope: !24, file: !3, line: 9, type: !18)
!24 = distinct !DILexicalBlock(scope: !15, file: !3, line: 9, column: 5)
!25 = !DILocation(line: 9, column: 13, scope: !24)
!26 = !DILocation(line: 9, column: 9, scope: !24)
!27 = !DILocation(line: 9, column: 18, scope: !28)
!28 = distinct !DILexicalBlock(scope: !24, file: !3, line: 9, column: 5)
!29 = !DILocation(line: 9, column: 19, scope: !28)
!30 = !DILocation(line: 9, column: 5, scope: !24)
!31 = !DILocation(line: 10, column: 14, scope: !32)
!32 = distinct !DILexicalBlock(scope: !28, file: !3, line: 9, column: 28)
!33 = !DILocation(line: 10, column: 11, scope: !32)
!34 = !DILocation(line: 11, column: 5, scope: !32)
!35 = !DILocation(line: 9, column: 23, scope: !28)
!36 = !DILocation(line: 9, column: 5, scope: !28)
!37 = distinct !{!37, !30, !38, !39}
!38 = !DILocation(line: 11, column: 5, scope: !24)
!39 = !{!"llvm.loop.mustprogress"}
!40 = !DILocation(line: 12, column: 12, scope: !15)
!41 = !DILocation(line: 12, column: 5, scope: !15)

生成的文件很长, 下面介绍一下基本的语法:

  • 以 “;” 开头的行是注释
  • 以 “!” 开头的行是结构定义, 通常用于结构体或调试信息处理
  • LLVM中包含两种基础类型标识符(identifiers), 分别是全局标识符(函数、全局变量)以”@”开头; 与局部标识符(寄存器、类型)以”%”开头. LLVM 中, 合法的标识符(类似于变量名函数明)包含以下三种类型:
    • 命名值(Named values), 为带有 [%@] 前缀的字符串, 所有能够使用正则表达式 [%@][-a-zA-Z$._][-a-zA-Z$._0-9]* 匹配到的都是合法的标识符;
    • 未命名值(Unnamed values), 为带有 [%@] 前缀的无符号数值;
    • 常量(Constants)
  • 定义变量语句, 如 %1 = alloca i32, align 4 语句, 表示在栈上分配一个i32大小的空间, 以4字节对齐; 注: 这里拿到的变量为对应的地址, 所有的变量本质上对应一个寄存器, 运算前需要解引用值
  • 读内存 load写内存 store:
    • load 命令从指定的内存地址读取数据, 并且将结果保存到一个寄存器中. 如语句 %1 = load i32, i32* %a, align 4 表示从 %a 的地址读取一个 i32 类型的数据, 并将结果存储在 %1 寄存器中.
    • store 命令将一个数据从一个寄存器保存到具体的内存地址. 如语句 store i32 %x, i32* %a, align 4 表示将 %x 寄存器的值, 保存在 %a 寄存器指向的地址中, 并四字节对齐.
  • 分支指令br, 即 branch 的缩写. 会根据给定条件决定程序的执行流程:
    • 无条件跳转: 例如 br label %<标签>. 如 br label %exit 表示无条件跳转到 exit 标签处;
    • 有条件跳转: 如理 br i1 %<布尔值>, label %<成立标签>, label %<不成立标签>. 如 br i1 %cond, label %then, label %else 表示如果 %cond 成立则跳转到 then 标签, 否则跳转到 else 标签.

文档信息

搜索

    Table of Contents