LLVM项目是模块化和可重用的编译器和工具链技术的集合。尽管它的名字,LLVM与传统的虚拟机没有什么关系。“LLVM”这个名字本身并不是一个缩写词;这是该项目的全称。
LLVM最初是伊利诺伊大学(University of Illinois)的一个研究项目,其目标是提供一种现代的、基于ssa的编译策略,能够支持任意编程语言的静态和动态编译。从那时起,LLVM已经发展成为一个由许多子项目组成的伞形项目,其中许多子项目被各种各样的商业和开源项目用于生产,也被广泛用于学术研究。LLVM项目中的代码在“Apache 2.0许可与LLVM例外”下获得许可。
LLVM的主要子项目有:
LLVM核心库提供了一个独立于源代码和目标的现代优化器,以及对许多流行cpu(以及一些不太常见的cpu)的代码生成支持!这些库是围绕一个被称为LLVM中间表示(“LLVM IR”)的指定良好的代码表示构建的。LLVM核心库有很好的文档,并且特别容易发明自己的语言(或移植现有的编译器)来使用LLVM作为优化器和代码生成器。
1.Clang是一个“LLVM原生”的C/ c++ /Objective-C编译器,旨在提供惊人的快速编译,非常有用的错误和警告消息,并为构建优秀的源代码级工具提供一个平台。Clang静态分析器和Clang -tidy是自动查找代码中的错误的工具,它们是可以使用Clang前端作为解析C/ c++代码的库来构建的工具的绝佳示例。
2.LLDB项目建立在LLVM和Clang提供的库之上,提供了一个很棒的本机调试器。它使用Clang ast和表达式解析器、LLVM JIT、LLVM反汇编器等,因此它提供了一种“刚刚工作”的体验。它在加载符号方面也比GDB快得多,内存效率也高得多。
3.libc++和libc++ ABI项目提供了c++标准库的标准一致性和高性能实现,包括对c++ 11和c++ 14的完全支持。
4.编译器-rt项目提供了低级代码生成器支持例程(如“__fixunsdfdi”)的高度调优实现,以及当目标没有短的本地指令序列来实现核心IR操作时生成的其他调用。它还为动态测试工具提供了运行时库的实现,如AddressSanitizer、ThreadSanitizer、MemorySanitizer和DataFlowSanitizer。
5.MLIR子项目是构建可重用和可扩展编译器基础设施的一种新方法。MLIR旨在解决软件碎片,改进异构硬件的编译,显著降低构建特定领域编译器的成本,并帮助将现有编译器连接在一起。
6.OpenMP子项目提供了与Clang中的OpenMP实现一起使用的OpenMP运行时。
7.polly项目使用多面体模型实现了一套缓存局部优化以及自动并行化和矢量化。
8.libclc项目旨在实现OpenCL标准库。
9.klee项目实现了一个“符号虚拟机”,它使用定理证明器来尝试评估程序中的所有动态路径,以查找错误并证明函数的属性。klee的一个主要特性是,当它检测到错误时,它可以生成一个测试用例。
10.LLD项目是一个新的链接器。这是系统连接器的直接替代品,运行速度快得多。
11.BOLT项目是一个链接后优化器。它通过基于抽样分析器收集的执行概要优化应用程序的代码布局来实现改进。
除了LLVM的官方子项目之外,还有各种各样的其他项目使用LLVM的组件来完成各种任务。通过这些外部项目,可以使用LLVM编译Ruby、Python、Haskell、Rust、D、PHP、Pure、Lua、Julia和许多其他语言。LLVM的一个主要优点是它的多功能性、灵活性和可重用性,这就是为什么它被用于各种不同的任务:从Lua等嵌入式语言的轻量级JIT编译到为大型超级计算机编译Fortran代码。
与其他一切一样,LLVM有一个广泛而友好的社区,人们对构建优秀的底层工具感兴趣。如果有兴趣参与其中,那么首先浏览LLVM博客并加入LLVM Discourse是一个不错的选择。有关如何发送补丁、获得提交权限以及版权和许可主题的信息,请参阅LLVM开发人员政策。
LLVM代码目录结构,可以看到大部分项目都包含在llvm-project中。
下载代码
git clone https://github.com/llvm/llvm-project.git
安装cmake,如果系统没有或者版本比较低,可以用如下方法快速安装
wget https://github.com/Kitware/CMake/releases/download/v3.23.1/cmake-3.23.1-linux-x86_64.sh
sudo ./cmake-3.23.1-linux-x86_64.sh --skip-license --exclude-subdir --prefix=/usr
安装依赖
sudo apt install build-essential
sudo apt install ninja-build
配置
cd llvm-project && mkdir build && cd build
cmake -G Ninja -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=../binary -DLLVM_TARGETS_TO_BUILD="X86;RISCV" -DLLVM_ENABLE_PROJECTS="clang;" -DLLVM_DEFAULT_TARGET_TRIPLE="riscv64-unknown-linux-gnu" ../llvm
编译,在build目录中,执行ninja
ninja
执行ninja install
llvm compile2
git clone -b release/12.x https://github.com/llvm/llvm-project && cd llvm-project
$ mkdir -p build && cd build
$ cmake ../llvm -GNinja -DCMAKE_INSTALL_PREFIX=$HOME/.local/llvm -DLLVM_ENABLE_PROJECTS="clang;clang-tools-extra;compiler-rt;cross-project-tests;libc;libclc;lld;lldb;openmp;polly;pstl" -DCMAKE_BUILD_TYPE=Release
$ ninja install
GLSL/SPIR-V编译环境搭建
git clone https://github.com/KhronosGroup/glslang.git
$ cd glslang/
$ python update_glslang_sources.py
$ mkdir build && cd build
$ cmake -DCMAKE_BUILD_TYPE=Debug -DCMAKE_INSTALL_PREFIX="../binary" ../
$ make -j8
$ make install
spir-v和LLVM转换环境
下载LLVM
caozilong@caozilong-Vostro-3268:~/Workspace$ git clone https://github.com/llvm/llvm-project.git
正克隆到 'llvm-project'...
remote: Enumerating objects: 5054222, done.
remote: Counting objects: 100% (2571/2571), done.
remote: Compressing objects: 100% (340/340), done.
remote: Total 5054222 (delta 2396), reused 2267 (delta 2231), pack-reused 5051651
接收对象中: 100% (5054222/5054222), 2.01 GiB | 1.72 MiB/s, 完成.
处理 delta 中: 100% (4139391/4139391), 完成.
正在检出文件: 100% (120564/120564), 完成.
$ cd llvm-project/llvm/projects
$ git clone https://github.com/KhronosGroup/SPIRV-LLVM-Translator.git
caozilong@caozilong-Vostro-3268:~/Workspace/llvm-project/llvm/projects$ git clone https://github.com/KhronosGroup/SPIRV-LLVM-Translator.git
正克隆到 'SPIRV-LLVM-Translator'...
remote: Enumerating objects: 25316, done.
remote: Counting objects: 100% (1169/1169), done.
remote: Compressing objects: 100% (631/631), done.
remote: Total 25316 (delta 640), reused 887 (delta 463), pack-reused 24147
接收对象中: 100% (25316/25316), 12.54 MiB | 6.89 MiB/s, 完成.
处理 delta 中: 100% (18563/18563), 完成.
编译
$ cd llvm-project
$ mkdir build && cd build
$ export PKG_CONFIG_PATH=/home/caozilong/Workspace/glslang/binary/lib/pkgconfig/
$ cmake -G Ninja -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=../binary -DLLVM_ENABLE_PROJECTS="clang;" ../llvm
$ ninja llvm-spirv
编译结果
验证编译结果
编写shader文件
#version 430
/**/
layout (location = 0) in vec3 vertPos;
layout (location = 1) in vec3 vertNormal;
vec4 varyingColor;
struct PositionalLight
{ vec4 ambient;
vec4 diffuse;
vec4 specular;
vec3 position;
};
struct Material
{ vec4 ambient;
vec4 diffuse;
vec4 specular;
float shininess;
};
vec4 globalAmbient;
PositionalLight light;
Material material;
mat4 mv_matrix;
mat4 proj_matrix;
mat4 norm_matrix;
void main(void)
{ vec4 color;
// convert vertex position to view space
vec4 P = mv_matrix * vec4(vertPos,1.0);
// convert normal to view space
vec3 N = normalize((norm_matrix * vec4(vertNormal,1.0)).xyz);
// calculate view-space light vector (from point to light)
vec3 L = normalize(light.position - P.xyz);
// view vector is negative of view space position
vec3 V = normalize(-P.xyz);
// R is reflection of -L around the plane defined by N
vec3 R = reflect(-L,N);
// ambient, diffuse, and specular contributions
vec3 ambient =
((globalAmbient * material.ambient)
+ (light.ambient * material.ambient)).xyz;
vec3 diffuse =
light.diffuse.xyz * material.diffuse.xyz
* max(dot(N,L), 0.0);
vec3 specular =
pow(max(dot(R,V), 0.0f), material.shininess)
* material.specular.xyz * light.specular.xyz;
// send the color output to the fragment shader
varyingColor = vec4((ambient + diffuse + specular), 1.0);
// send the position to the fragment shader, as before
gl_Position = proj_matrix * mv_matrix * vec4(vertPos,1.0);
}
caozilong@caozilong-Vostro-3268:~/Workspace/glslang/binary/bin$ ./glslangValidator -V -S vert -o vertex.spv vertex.gls
vertex.gls
caozilong@caozilong-Vostro-3268:~/Workspace/glslang/binary/bin$ ls -l
总用量 592276
-rwxr-xr-x 1 caozilong caozilong 180650664 10月 3 09:56 glslangValidator
-rwxr-xr-x 1 caozilong caozilong 2903864 10月 3 09:53 spirv-as
-rwxr-xr-x 1 caozilong caozilong 2480184 10月 3 09:53 spirv-cfg
-rwxr-xr-x 1 caozilong caozilong 3485912 10月 3 09:53 spirv-dis
-rwxr-xr-x 1 caozilong caozilong 866 10月 2 21:36 spirv-lesspipe.sh
-rwxr-xr-x 1 caozilong caozilong 69247904 10月 3 09:55 spirv-link
-rwxr-xr-x 1 caozilong caozilong 67723632 10月 3 09:56 spirv-lint
-rwxr-xr-x 1 caozilong caozilong 156290072 10月 3 09:55 spirv-opt
-rwxr-xr-x 1 caozilong caozilong 92484416 10月 3 09:56 spirv-reduce
-rwxr-xr-x 1 caozilong caozilong 2019096 10月 3 09:56 spirv-remap
-rwxr-xr-x 1 caozilong caozilong 29148608 10月 3 09:53 spirv-val
-rw-rw-r-- 1 caozilong caozilong 1474 10月 3 10:42 vertex.gls
-rw-rw-r-- 1 caozilong caozilong 3576 10月 3 10:45 vertex.s
片段着色器
./glslangValidator -V -S frag -o pixel.spv pixel.gls
#version 460 core
void main(void)
{
// Do Nothing...
}
caozilong@caozilong-Vostro-3268:~/Workspace/glslang/binary/bin$ ./glslangValidator -V -S frag -o pixel.spv pixel.gls
pixel.gls
caozilong@caozilong-Vostro-3268:~/Workspace/glslang/binary/bin$ xxd -g 1 pixel.gls
00000000: 23 76 65 72 73 69 6f 6e 20 34 36 30 20 63 6f 72 #version 460 cor
00000010: 65 0a 0a 76 6f 69 64 20 6d 61 69 6e 28 76 6f 69 e..void main(voi
00000020: 64 29 0a 7b 0a 20 20 20 20 2f 2f 20 44 6f 20 4e d).{. // Do N
00000030: 6f 74 68 69 6e 67 2e 2e 2e 0a 7d 0a 0a othing....}..
caozilong@caozilong-Vostro-3268:~/Workspace/glslang/binary/bin$ xxd -g 1 pixel.spv
00000000: 03 02 23 07 00 00 01 00 0a 00 08 00 06 00 00 00 ..#.............
00000010: 00 00 00 00 11 00 02 00 01 00 00 00 0b 00 06 00 ................
00000020: 01 00 00 00 47 4c 53 4c 2e 73 74 64 2e 34 35 30 ....GLSL.std.450
00000030: 00 00 00 00 0e 00 03 00 00 00 00 00 01 00 00 00 ................
00000040: 0f 00 05 00 04 00 00 00 04 00 00 00 6d 61 69 6e ............main
00000050: 00 00 00 00 10 00 03 00 04 00 00 00 07 00 00 00 ................
00000060: 03 00 03 00 02 00 00 00 cc 01 00 00 05 00 04 00 ................
00000070: 04 00 00 00 6d 61 69 6e 00 00 00 00 13 00 02 00 ....main........
00000080: 02 00 00 00 21 00 03 00 03 00 00 00 02 00 00 00 ....!...........
00000090: 36 00 05 00 02 00 00 00 04 00 00 00 00 00 00 00 6...............
000000a0: 03 00 00 00 f8 00 02 00 05 00 00 00 fd 00 01 00 ................
000000b0: 38 00 01 00 8...
spir-v IR转LLVM
SPIR-V对于Vulkan而言是仅有的官方支持的着色语言。它在API层被接受并且最终用于构造流水线,这些流水线是配置一个Vulkan设备的对象,为你的应用完成工作。SPIR-V被设计为对一些工具和驱动而言非常容易处理的表示。这通过不同实现之间的多样性来提升可移植性。一个SPIR-V模块的内部表示是一条32位字的流,存放在存储器中。除非你是一位工具写手或计划自己生成SPIR-V,否则的话你不太需要直接处理SPIR-V的二进制编码。而是说,你要么可以看SPIR-V的可读的文本表示,或是使用诸如 glslangvalidator 这样的官方Khronos GLSL编译工具来生成SPIR-V。
首先用spirv-dis工具反编译上面生成的.spv文件,生成 SPIRV IR文件,而SPIRV IR文件可以转换为LLVM IR文件。
./spirv-dis -o spirv.dis pixel.spv
./spirv-dis -o spirv2.dis vertex.spv
经过下面两步将代码转换为LLVM IR
caozilong@caozilong-Vostro-3268:~/Workspace/glslang/binary/bin/spirv-as --target-env spv1.0 spirv2.dis -o spirv-ir.ir
caozilong@caozilong-Vostro-3268:~/Workspace/glslang/binary/bin$ ../../../llvm-project/build/bin/llvm-spirv -r spirv-ir.ir
InvalidBuiltinSetName: Expects OpenCL.std. Actual is GLSL.std.450
caozilong@caozilong-Vostro-3268:~/Workspace/glslang/binary/bin$ ../../../llvm-project/build/bin/llvm-spirv -r spirv-ir.ir
InvalidBuiltinSetName: Expects OpenCL.std. Actual is GLSL.std.450
最后出错了,有空再分析。
中间代码之前的部分叫做前端,中间代码之后的部分叫做后端,LLVM不同的就是对于不同的语言他都提供了同一种中间表示.如下传统GCC和LLVM的对比:
要将源代码转换为LLVM IR位码,源代码必须经过几个中间步骤,下图说明了这个步骤:
GCC的与处理程序是CPP
查看默认预定义宏的方式为:
$ touch foo.h
$ cpp -dM foo.h
$ gcc -E -dN foo.h
$ gcc -E -DDBG=1 -dN foo.h
DBG宏定义会出现在command-line
gcc -E 的输出的数字是啥意思?
Preprocessor Output (The C Preprocessor)
经过请教编译器专家和询问CHAT GPT,总算有所收获,记录如下:
在 LLVM 中添加自定义指令涉及多个步骤,需要对 LLVM 的前端(例如 Clang)和后端(代码生成器)进行修改。下面是一个大致的步骤指南,但请注意这只是一个概述,具体步骤可能因需求和 LLVM 版本而有所不同。
定义新指令: 以新增快速傅里叶变换FFT指令为例,首先,需要定义的自定义指令FFT,包括FFT对应的操作码、操作数和指令格式。这通常需要在 LLVM 源代码中的相应文件中进行。确保的指令与 LLVM IR 中的数据流图匹配,定义在LLVM TARGET描述文件中。
Clang 前端修改: 如果自定义指令需要在源代码级别进行标识和使用,需要修改 Clang 前端以支持新指令。这可能包括扩展语法解析、代码生成和优化等步骤。目的是让前端识别新增的语言内建标识符,注意是标识符,而不是库函数名,是不需要连接任何库即可由编译器自身识别的标识符,比如sizeof,new, int之类的。
IR 转换: 如果希望将自定义指令添加到 LLVM IR 中,需要修改 LLVM 的 IR 转换逻辑,以便它能够处理和优化新指令。
代码生成器修改: 要让 LLVM 后端能够将自定义指令转换为目标体系结构的指令,需要修改 LLVM 后端的代码生成器。这包括定义如何将自定义指令映射到目标指令,并生成正确的机器代码。
指令选择: LLVM 后端的指令选择阶段负责将高级 IR 转换为低级 IR(机器代码)。需要修改指令选择逻辑,以便它能够选择并生成自定义指令。
测试和验证: 在进行任何修改之前,确保理解 LLVM 架构并且对所做的更改进行测试。这可能涉及使用测试套件和样例代码来验证新指令是否按预期工作。
添加自定义指令是一个复杂的任务,需要对 LLVM 架构和内部工作原理有深入的理解。可能需要查阅 LLVM 的文档、源代码和相关资源,此外,不同版本的 LLVM 可能有不同的步骤和细节,需要留意。
经过请教编译器开发者,添加指令并不是简单的修改一下前端和后端就可以了,还需要主动建立前段标识符到后端ISA指令之间的联系,这个过程可能涉及到LLVM的方方面面。
LLVM Pass入门导引 - 知乎