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 架构指令;
同时, LLVM 包含了以下多个子项目:
项目名称 | 描述 |
---|---|
LLVM Core | 包含一个源代码和目标架构无关的独立配置器,一个针对很多主流(甚至于一些非主流)的CPU的汇编代码生成支持。这些核心库围绕IR来构建。 |
Clang | 一个C/C++/Objective-C编译器,提供高效快速的编译效率,风格良好、极其有用的错误和警告信息。 |
LLDB | 基于LLVM提供的库和Clang构建的优秀的本地调试器。原生支持调试多线程程序。 |
LLD | clang/llvm内置的链接器 |
dragonegg | gcc插件,可将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框架,使用多面体模型实现一组缓存局部优化以及自动并行和矢量化。 |
libclc | OpenCL(开放运算语言)标准库的实现. |
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_FRONTEND
与 TZ
为使用 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)
- 命名值(Named values), 为带有
- 定义变量语句, 如
%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 标签.
- 无条件跳转: 例如
文档信息
- 本文作者:Birkhoff
- 本文链接:https://blogs.maikebuke.com/2023/02/18/01-LLVM-%E5%AE%89%E8%A3%85/
- 版权声明:自由转载-非商用-非衍生-保持署名(创意共享3.0许可证)