【编译器】-LLVMIR
概述
LLVM 是一种基于静态单赋值 (SSA) 的表示形式,提供类型安全、低级操作、灵活性以及干净地表示“所有”高级语言的能力。
- LLVM IR 是一门低级语言,语法类似于汇编
- 任何高级编程语言(如C++)都可以用LLVM IR表示
- 基于LLVM IR可以很方便地进行代码优化
LLVM IR(Intermediate Representation,中间表示)有三种主要的表现形式,这些形式允许编译器在不同的阶段以适合的方式操作和处理代码。这三种表现形式是:
- 文本形式(Textual Form)
- 二进制形式(Binary Form,又称 Bitcode)
- 内存中的数据结构(In-Memory Data Structures)
文本形式是 LLVM IR 的人类可读版本,通常用作调试和分析工具。它使用类似汇编语言的语法,描述编译器生成的中间代码。可以通过 LLVM 工具(如 llvm-dis)将 LLVM Bitcode 转换为文本形式,或者通过 clang 编译器生成。但体积较大,不适合高效存储和传输。
- LLVM IR 的文本形式包含全局变量、函数、指令、类型、元数据等内容。
- 它具备结构化语法,与汇编语言类似,但具有高级语言特性,如面向对象、类型系统和 SSA(Static Single Assignment,静态单赋值)形式。
define i32 @sum(i32 %a, i32 %b) { %1 = add i32 %a, %b ret i32 %1 }
二进制形式 是 LLVM IR 的紧凑编码形式,称为 Bitcode。它是 LLVM IR 的序列化格式,设计为编译器内部和外部使用,以便高效存储、传输和重用。
- Bitcode 是 LLVM 的高效二进制格式,结构紧凑,适合存储在磁盘上或在网络中传输。
- Bitcode 是目标无关的,因此可以在不同的硬件平台上重新使用。
- Bitcode 保留了完整的 LLVM IR 信息,可以反序列化为文本形式或直接用于生成机器代码。
LLVM 编译器可以生成 Bitcode 文件,通常以 .bc 后缀保存。例如,可以通过 clang 命令生成 Bitcode 文件,可以使用 llvm-dis 将 Bitcode 转换回文本形式。
clang -emit-llvm -c input.c -o output.bcllvm-dis output.bc -o output.ll
内存中的数据结构 是 LLVM IR 的第三种表现形式,通常用于编译器在内存中对中间表示进行操作和优化。
- 在编译过程中的不同阶段,LLVM IR 会以内存中的数据结构形式存在,表示为 llvm::Module、llvm::Function、llvm::BasicBlock、llvm::Instruction 等对象。
- 内存数据结构为编译器提供了操作 IR 的 API,允许编译器或工具链进行优化、分析、代码生成等操作。
llvm::LLVMContext Context;
llvm::Module *Module = new llvm::Module("test", Context);
llvm::IRBuilder<> Builder(Context);
llvm::FunctionType *FuncType = llvm::FunctionType::get(Builder.getInt32Ty(), false);
llvm::Function *Func = llvm::Function::Create(FuncType, llvm::Function::ExternalLinkage, "foo", Module);
LLVM IR结构
- 源代码被编译为LLVM IR后,具有以下结构:

模块 Module
- 一个源代码对应LLVM IR中的一个模块。
- 头部信息包含程序的目标平台,如X86、ARM等,和一些其他信息。
- 全局符号包含全局变量、函数的定义与声明。
每个模块由函数、全局变量和符号表条目组成。模块可以与 LLVM 链接器组合在一起,它合并函数(和全局变量)定义、解析前向声明并合并符号表条目。一般来说,模块由全局值列表组成(其中函数和全局变量都是全局值)。全局值由指向内存位置的指针(在本例中为指向 char 数组的指针和指向函数的指针)表示
target triple 是一个描述目标平台的字符串,通常由三个或四个部分组成,它标识了 LLVM 编译器生成的目标机器的体系结构、供应商、操作系统和可选的环境或 ABI(应用程序二进制接口)。
---
target datalayout 字段描述了目标机器的数据布局规则。它告诉编译器在特定的硬件架构上如何排列数据、对齐内存、以及如何计算数据类型的大小等。
target datalayout = “e-m:e-i64:64-f80:128-n8:16:32:64-S128”
函数 Function - LLVM IR中的函数表示源代码中的某个函数。
- 参数,顾名思义为函数的参数。
- 一个函数由若干基本块组成,其中函数最先执行的基本块为入口块。
基本块 BasicBlock - 一个基本块由若干个指令和标签组成。
- 正常情况下,基本块的最后一条指令为跳转指令(br或者switch),或返回指令(retn),也叫作终结指令(Terminator Instruction)。
- PHI指令是一种特殊的指令。
LLVM IR语法
LLVM 标识符有两种基本类型:全局标识符和本地标识符。全局标识符(函数、全局变量)以’@’ 字符开头。本地标识符(寄存器名称、类型)以 '%'字符开头。
标识符有三种不同的格式:
- 命名值表示为带有前缀的字符串。例如,%foo,@DivisionByZero, %a.really.long.identifier。实际使用的正则表达式是“ [%@][-a-zA-Z . ] [ − a − z A − Z ._][-a-zA-Z .][−a−zA−Z._0-9]*”
- 未命名的值表示为带有前缀的无符号数值。例如,%12,@2,%44。
- 常量
LLVM 程序中的字符串由字符分隔"。在字符串中,除\ 开始转义的字符和"结束字符串的第一个字符外,所有字节均按字面意思处理。
- 要表示一个"字符,请使用\22. ("将以尾随 结束字符串\。)
- 换行符不会终止字符串常量;字符串可以跨越多行。
- 字符串常量的解释(例如它们的字符编码)取决于上下文。
所有全局变量和函数都具有以下链接类型之一: - private:具有“ ”链接的全局值private只能由当前模块中的对象直接访问。
- linkonce:当链接发生时,具有“ linkonce”链接的全局变量将与其他同名全局变量合并。
- external:如果没有使用上述标识符,则全局是外部可见的,这意味着它参与链接并可用于解析外部符号引用。
所有全局变量和函数都具有以下可见性样式之一:
“ default” - 默认样式:在使用 ELF 对象文件格式的目标上,默认可见性意味着声明对其他模块可见,并且在共享库中意味着声明的实体可以被覆盖。
“ hidden”——隐藏风格:如果具有隐藏可见性的对象位于同一共享对象中,则它们的两个声明将引用同一对象。
“ protected” - 受保护的样式:在 ELF 上,受保护的可见性表示符号将放置在动态符号表中,但定义模块内的引用将绑定到本地符号。也就是说,该符号不能被另一个模块覆盖。
数据表示
第一类类型
第一类类型是可以被直接传递给函数、保存在内存中、从函数返回的类型。
整数类型:为所需的整数类型指定任意位宽度。可以指定从 1 位到 2 (23) (约 800 万)的任意位宽。
i1
一位整数。
i32
32 位整数。
i1942652
一个超过 100 万位的大整数。
浮点类型:
暂时无法在飞书文档外展示此内容
指针类型:ptr用于指定内存位置。指针通常用于引用内存中的对象。指针类型可以具有可选的地址空间属性,定义所指向的对象所在的编号地址空间。
向量类型:向量类型是表示元素向量的简单派生类型。向量类型需要大小(元素数量)、基础原始数据类型和可扩展属性来表示向量,其中确切的硬件向量长度在编译时未知。
标签类型:标签类型代表代码标签。
令牌类型:当值与指令关联时使用令牌类型。
元数据类型:表示嵌入的元数据。
结构类型:用于表示内存中数据成员的集合。结构的元素可以是具有大小的任何类型。
常量
简单常量:布尔常量、整数常量、浮点常量、空指针常量、令牌常量
复数常量:结构常量、数组常量、向量常量、元素常量
undef:字符串 ’ undef’ 可以用在需要常量的任何地方,并指示该值的用户可能会收到未指定的位模式。
数据区里的数据
在LLVM IR中定义一个存储在数据区中的全局变量,其格式为:
@global_variable = global i32 0
如果是只读的全局变量,也就是常量,我们可以用constant来代替global:
@global_constant = constant i32 0
在LLVM IR中,所有的全局变量的名称都需要用@开头。
链接类型
对于链接类型,常用的主要有什么都不加(默认为external)、private和internal。
用private,则代表这个变量的名字不会出现在符号表中。我们将原来的代码改写成
@global_variable = private global i32 0
用internal则表示这个变量是以局部符号的身份出现(全局变量的局部符号,可以理解成C中的static关键词)。我们将原来的代码改写成
@global_variable = internal global i32 0
寄存器内的数据和栈上的数据
全局变量和栈上变量皆指针
LLVM IR把它们都看作指针。也就是说,对于全局变量:
@global_variable = global i32 0
和栈上变量
%local_variable = alloca i32
寄存器
在LLVM IR中,一个函数的局部变量可以是寄存器或者栈上的变量。对于寄存器而言,我们只需要像普通的赋值语句一样操作,但需要注意名字必须以%开头:
%local_variable = add i32 1, 2
栈
当不需要操作地址并且寄存器数量足够时,我们可以直接使用寄存器。而LLVM IR的策略保证了我们可以使用无数的虚拟寄存器。那么,在需要操作地址以及需要可变变量(之后会提到为什么)时,我们就需要使用栈。
%local_variable = alloca i32
SSA
LLVM IR是一个严格遵守SSA(Static Single Assignment)策略的语言。SSA的要求很简单:每个变量只被赋值一次。
把可变变量放到全局变量或者栈内变量里,虚拟寄存器只存储不可变的变量。比如说,我想实现上面的功能,把两次运算结果储存到同一个变量内:
%stack_variable = alloca i32
%1 = add i32 1, 2
store i32 %1, i32* %stack_variable
%2 = add i32 3, 4
store i32 %2, i32* %stack_variable
基本类型(Primitive Types)
LLVM IR中的基本类型是构建所有复杂类型的基础,它们直接对应硬件支持的原生数据类型。
a. 整数类型
iN: 表示一个N位的整数类型,其中N是整数位宽。LLVM支持任意位宽的整数(不局限于常见的8、16、32、64位),例如i1、i8、i16、i32、i64等。
%1 = add i32 %a, %b ; 定义一个32位整数加法
i1: 表示布尔类型,i1通常用于条件判断和比较操作。i1的值为0(假)或1(真)。
%cond = icmp eq i32 %a, %b ; 比较是否相等,结果为i1类型
b. 浮点数类型
half: 16位半精度浮点数。
float: 32位单精度浮点数。
double: 64位双精度浮点数。
fp128: 128位浮点数。
%1 = fadd float %a, %b ; 定义一个32位浮点数加法
%2 = fmul double %x, %y ; 定义一个64位浮点数乘法
c. 其他基本类型
void: 表示无返回值的类型,通常用于函数的返回类型,表示该函数不返回任何值。
label: 表示跳转目标的标签类型,通常用于表示代码块的目标。
metadata: 元数据类型,用于附加到指令、全局变量和其他代码结构中的辅助信息,LLVM不对其执行。
x86_mmx: 表示x86架构中的MMX向量类型。
指针类型(Pointer Types)
指针类型是LLVM IR中用于引用内存地址的类型。所有指针在LLVM IR中都显示为类型化指针,即指向特定类型的指针。指针类型用*表示,表示指向类型的指针。
指针类型可以指向任意类型(例如整数、浮点数、结构体、数组等),并且可以进行算术和比较操作。
%ptr = alloca i32 ; 分配一个i32的指针
%nullPtr = null i32* ; 定义一个空指针
getelementptr 指令:用于计算指针的偏移量
%arrayElemPtr = getelementptr [10 x i32], [10 x i32]* %array, i32 0, i32 5 ; 获取数组中第6个元素的指针
数组类型(Array Types)
数组类型用于表示一组相同类型的元素。LLVM中的数组类型用[N x ]表示,其中N是数组的大小,是数组元素的类型。
- 数组是固定大小的,大小必须在编译时已知。
- 数组元素通过getelementptr指令访问,通过偏移计算来获得元素地址。
@myArray = global [10 x i32] [i32 1, i32 2, i32 3, i32 4, i32 5, i32 6, i32 7, i32 8, i32 9, i32 10] ; 定义一个10元素的i32数组
结构体类型(Structure Types)
结构体类型是LLVM中的复合类型,表示一组不同类型的数据的组合。结构体类型用{, , …}的形式表示。 - 结构体可以包含任意类型的元素,包括基本类型、数组类型、指针类型等。
- 结构体的元素通过getelementptr指令来访问,类似于数组的元素访问。
%myStruct = type { i32, float } ; 定义一个包含i32和float的结构体类型
%structPtr = alloca %myStruct ; 在栈上分配结构体
%fieldPtr = getelementptr %myStruct, %myStruct* %structPtr, i32 0, i32 1 ; 获取结构体的第二个字段
函数类型(Function Types)
函数类型描述了一个函数的返回类型和参数类型。函数类型用<return_type>(<arg_type1>, <arg_type2>, …)的形式表示。 - LLVM IR中的函数是类型化的,每个函数都有明确的返回类型和参数类型。
- 函数类型支持可变参数(vararg),类似于C语言中的可变参数函数。
- 函数的返回类型可以是void,表示该函数不返回值。
define i32 @myFunction(i32 %a, float %b) {
; 函数体
ret i32 %a
}
Label类型(Label Type)
label类型用于表示跳转目标的标签,主要用于分支跳转和循环控制结构中。label类型没有直接的值,而是作为跳转目标存在。
br label %label1 ; 跳转到label1标签
label1: ; 定义标签label1
常用指令
LLVM IR指令系统由一组用于表示程序的操作的指令组成,这些指令被设计为抽象的、简洁的并适合多种架构。LLVM IR中的指令既可以表示常见的运算操作,又可以用于表示控制流、内存操作、类型转换、以及并行化和特殊用途的指令等。
- 算术指令(Arithmetic Instructions)
算术指令用于基本的整数和浮点数运算。
a. 整数算术指令
add: 整数加法。
%result = add i32 %a, %b ; 将a和b相加,返回i32类型结果
sub: 整数减法。
%result = sub i32 %a, %b ; 将a减去b,返回i32类型结果
mul: 整数乘法。
%result = mul i32 %a, %b ; 将a和b相乘,返回i32类型结果
udiv / sdiv: 无符号/有符号整数除法。
%result = udiv i32 %a, %b ; 无符号整数除法
%result = sdiv i32 %a, %b ; 有符号整数除法
urem / srem: 无符号/有符号整数取余。
%result = urem i32 %a, %b ; 无符号整数取余
%result = srem i32 %a, %b ; 有符号整数取余
b. 浮点数算术指令
fadd: 浮点数加法。
%result = fadd float %a, %b ; 将a和b相加,返回浮点数结果
fsub: 浮点数减法。
%result = fsub float %a, %b ; 将a减去b,返回浮点数结果
fmul: 浮点数乘法。
%result = fmul float %a, %b ; 将a和b相乘,返回浮点数结果
fdiv: 浮点数除法。
%result = fdiv float %a, %b ; 将a除以b,返回浮点数结果
frem: 浮点数取余。
%result = frem float %a, %b ; 浮点数取余操作
2. 位运算指令(Bitwise Instructions)
位运算指令用于执行位操作,主要用于整数类型。
and: 按位与。
%result = and i32 %a, %b ; 将a和b按位与
or: 按位或。
%result = or i32 %a, %b ; 将a和b按位或
xor: 按位异或。
%result = xor i32 %a, %b ; 将a和b按位异或
shl: 左移操作。
%result = shl i32 %a, %b ; 将a左移b位
lshr: 逻辑右移(无符号)。
%result = lshr i32 %a, %b ; 将a逻辑右移b位
ashr: 算术右移(有符号)。
%result = ashr i32 %a, %b ; 将a算术右移b位
3. 内存访问指令(Memory Access Instructions)
这些指令用于在内存中加载和存储数据。
alloca: 分配栈空间。
%ptr = alloca i32 ; 在栈上分配一个i32类型的空间
load: 从内存中加载数据。
%val = load i32, i32* %ptr ; 从指针%ptr指向的地址加载i32值
store: 将数据存储到内存中。
store i32 %val, i32* %ptr ; 将i32类型的值%val存储到指针%ptr指向的地址中
getelementptr (GEP):计算数组、结构体中的元素地址。
%elemPtr = getelementptr [10 x i32], [10 x i32]* %array, i32 0, i32 2
; 获取数组中第3个元素的指针
4. 控制流指令(Control Flow Instructions)
控制流指令用于程序的跳转、条件分支、函数调用等。
a. 跳转和分支
br: 条件或无条件分支跳转。
br label %target ; 无条件跳转到标签target
br i1 %cond, label %ifTrue, label %ifFalse ; 条件分支,若%cond为真则跳转到ifTrue,否则跳转到ifFalse
switch: 类似C语言中的switch语句,进行多分支选择。
switch i32 %val, label %default [i32 1, label %case1, i32 2, label %case2]
b. 函数调用
call: 调用一个函数。
%result = call i32 @myFunction(i32 %arg1, float %arg2)
ret: 返回函数结果。
ret i32 %result ; 返回i32类型的结果
c. 不正常终止
unreachable: 表示程序不可达位置,编译器假定不会到达该指令。
unreachable
5. 类型转换指令(Conversion Instructions)
类型转换指令用于在不同类型之间进行转换,确保类型安全。
trunc: 截断,将大类型缩减为小类型。
%small = trunc i32 %big to i8 ; 将32位整数截断为8位整数
zext / sext: 零扩展/符号扩展,将小类型扩展为大类型。
%big = zext i8 %small to i32 ; 将8位整数零扩展为32位
fptrunc / fpext: 浮点数截断/扩展。
%smallFloat = fptrunc double %bigFloat to float ; 截断双精度浮点数为单精度
bitcast: 位转换,不改变位模式,只改变解释方式。
%ptr = bitcast i32* %a to float* ; 将i32指针解释为float指针
addrspacecast: 在不同地址空间之间转换指针。
%ptr2 = addrspacecast i32 addrspace(1)* %ptr to i32 addrspace(2)*
6. 比较指令(Comparison Instructions)
这些指令用于对整数、浮点数进行比较,返回布尔类型的i1值。
a. 整数比较指令
icmp: 整数比较指令,支持多种条件(例如相等、大小关系等)。
%isEqual = icmp eq i32 %a, %b ; 判断a是否等于b
%isGreater = icmp sgt i32 %a, %b ; 判断a是否大于b(有符号比较)
b. 浮点数比较指令
fcmp: 浮点数比较指令。
%isEqual = fcmp oeq float %a, %b ; 判断a是否等于b
%isGreater = fcmp ogt float %a, %b ; 判断a是否大于b
7. 原子和同步指令(Atomic and Synchronization Instructions)
这些指令用于并发编程中的原子操作和同步控制。
atomicrmw: 原子读-修改-写操作。
%result = atomicrmw add i32* %ptr, i32 1 seq_cst
; 原子地将1加到%ptr指向的i32值中
cmpxchg: 比较并交换指令。
%old = cmpxchg i32* %ptr, i32 %oldVal, i32 %newVal seq_cst
fence: 用于保证内存访问顺序。
fence acquire
8. 其他指令
a. phi节点
用于在SSA(单静态赋值)形式中选择来自不同控制流路径的值。
%x = phi i32 [ 0, %entry ], [ %y, %loop ]
b. 内联汇编(Inline Assembly)
允许在LLVM IR中嵌入目标架构的汇编代码。
call void asm “mov $0, %eax”, “r”(i32 %val)
c. select
条件选择指令,根据布尔条件选择不同的值。
%result = select i1 %cond, i32 %a, i32 %b ; 如果%cond为true则选择a,否则
函数
在LLVM IR(中间表示)中,函数是程序的基本构建块。它们可以被视为一组指令的集合。LLVM IR中的函数不仅描述了函数的签名,还包括其内部的控制流、局部变量、参数传递和返回值的处理等。
- 函数定义(Function Definition)
在LLVM IR中,函数的定义由其返回类型、函数名、参数类型、以及可选的函数属性组成。
define <return_type> @function_name(<param_type1> %param1, <param_type2> %param2, …) {
; function body
} - 函数参数(Function Parameters)
函数的参数在定义时声明,并在调用时传递。参数在函数体内部以局部变量的形式使用。
- 参数可以是基本类型、结构体类型、指针类型等。
- 参数的顺序和类型在函数调用时必须匹配。
- 返回值(Return Values)
LLVM IR函数可以有一个返回值,使用ret指令来返回。返回值的类型必须与函数定义中的返回类型一致。
LLVM IR不直接支持返回多个值,但可以通过结构体或元组等复合类型来实现
%result = call {i32, i32} @function_returning_struct(i32 %a, i32 %b) - 函数调用(Function Call)
函数调用通过call指令完成,传递参数并接收返回值。
%result = call i32 @add(i32 10, i32 20)
LLVM IR支持可变参数函数,使用…表示参数列表的可变性。
define i32 @sum(i32 %count, …) {
; 函数体
} - 函数属性(Function Attributes)
LLVM 函数定义由“ define”关键字、可选的链接类型、可选的运行时抢占说明符、可选的可见性样式、可选的DLL 存储类、可选的调用约定、可选的unnamed_addr属性、返回类型、可选的 参数属性组成。
define [linkage] [PreemptionSpecifier] [visibility] [DLLStorageClass]
[cconv] [ret attrs]
@ ([argument list])
[(unnamed_addr|local_unnamed_addr)] [AddrSpace] [fn Attrs]
[section “name”] [partition “name”] [comdat [($name)]] [align N]
[gc] [prefix Constant] [prologue Constant] [personality Constant]
(!name !N)* { … }
LLVM 函数声明由“ declare”关键字、可选链接类型、可选可见性样式、可选DLL 存储类、可选调用约定、可选unnamed_addr orlocal_unnamed_addr属性、可选地址空间、返回类型、可选参数属性组成。
函数属性用于指定优化行为和调用约定,常见的属性包括:
- nounwind: 指示函数不会抛出异常。
- readonly: 函数不会修改任何全局状态。
- writeonly: 函数不会读取任何参数。
- tail: 尾调用优化指示,允许编译器进行尾调用优化。
内联函数在调用时会被替换为函数体的内容,以减少函数调用的开销。LLVM IR支持将函数标记为内联。
define void @inline_function() alwaysinline { ; 函数体 }
LLVM IR支持控制函数的可见性,使用不同的链接属性来控制符号的暴露。 - private: 仅在当前模块可见。
- internal: 在模块内可见,但不能导出。
- external: 其他模块可见,可以被调用。
define internal i32 @internal_function() { ; 函数体 }
内部函数
LLVM IR 中的内部函数(Intrinsics)是提供特定功能的内建函数,通常用于对低级硬件操作的封装或为某些特定平台提供优化。内部函数不像普通函数,它们是编译器特有的,用于向LLVM传递某些特定的指令或操作提示。
这些内部函数以llvm.为前缀,通过函数调用语法使用,但实际上它们不会生成常规的函数调用,而是直接转化为底层机器指令或特定的编译优化操作。它们可以在代码优化、平台相关操作、特殊内存管理、向量化等方面发挥重要作用。
内部函数的定义与特性
LLVM 内部函数(intrinsics)与普通函数有以下不同特性:
- 前缀:内部函数以llvm.为前缀,例如llvm.memcpy.p0i8.p0i8.i64。
- 不生成普通调用指令:它们直接映射到底层指令,或在某些情况下成为编译时的优化提示。
- 跨平台支持:很多内部函数具有跨平台能力,例如向量操作和内存管理。
- 特定目的:用于硬件加速、内存访问控制、并行计算支持等特殊功能。
内存操作相关的内部函数
这些内部函数用于执行各种内存相关的操作,如复制、设置、加载和存储。
llvm.memcpy.*: 复制内存区域。
call void @llvm.memcpy.p0i8.p0i8.i64(i8* %dest, i8* %src, i64 %size, i1 false)llvm.memmove.*: 内存块重叠时的内存移动。
call void @llvm.memmove.p0i8.p0i8.i64(i8* %dest, i8* %src, i64 %size, i1 false)llvm.memset.*: 将某个字节值填充到内存区域。
call void @llvm.memset.p0i8.i64(i8* %dest, i8 0, i64 %size, i1 false)llvm.lifetime.start 和 llvm.lifetime.end: 用于标记内存的生命周期提示。
call void @llvm.lifetime.start.p0i8(i64 64, i8* %ptr)
call void @llvm.lifetime.end.p0i8(i64 64, i8* %ptr)
数学运算相关的内部函数
这些内部函数提供对硬件加速的数学操作支持,通常用于加速计算密集型应用。
llvm.sqrt.*: 计算平方根。
%result = call float @llvm.sqrt.f32(float %val)llvm.pow.*: 计算幂。
%result = call double @llvm.pow.f64(double %base, double %exp)llvm.sin.*, llvm.cos.*, llvm.log.*: 常用的三角函数、对数函数等数学操作。
位操作相关的内部函数
这些内部函数用于处理低级的位操作。
llvm.ctlz.*: 计算从高位开始的连续0的个数(Count Leading Zeros)。
%zeros = call i32 @llvm.ctlz.i32(i32 %val, i1 false)llvm.cttz.*: 计算从低位开始的连续0的个数(Count Trailing Zeros)。
%zeros = call i32 @llvm.cttz.i32(i32 %val, i1 false)llvm.bswap.*: 交换字节顺序(Byte Swap),常用于大端和小端之间的转换。
%swapped = call i32 @llvm.bswap.i32(i32 %val)
原子操作和同步相关的内部函数
这些内部函数提供对并发编程中原子操作和同步的支持。
llvm.atomicrmw.*: 原子读-修改-写操作。%result = atomicrmw add i32* %ptr, i32 1 seq_cstllvm.cmpxchg.*: 比较并交换(Compare-and-Swap)。
%old = cmpxchg i32* %ptr, i32 %oldVal, i32 %newVal seq_cstllvm.fence.*: 内存屏障,用于保证多线程之间的内存访问顺序。
fence acquire
控制流相关的内部函数
这些函数用于控制LLVM IR中代码生成或优化过程中的控制流逻辑。
llvm.expect.*: 提示编译器某个布尔条件在运行时更可能为true或false,从而进行分支预测优化。
%likely = call i1 @llvm.expect.i1(i1 %condition, i1 true)
栈操作相关的内部函数
这些内部函数用于管理栈内存分配,特别是在内存分配不确定的场景中。
llvm.stacksave 和 llvm.stackrestore: 保存和恢复栈指针。
%stack_ptr = call i8* @llvm.stacksave()
call void @llvm.stackrestore(i8* %stack_ptr)llvm.alloca: 动态分配栈空间,类似C语言中的alloca函数。
%ptr = alloca i32
向量化和SIMD(单指令多数据)相关的内部函数
LLVM提供了一系列的内部函数来支持向量化操作,特别是在处理并行数据时。
llvm.vector.reduce.add.*: 向量加法的归约操作。
%sum = call float @llvm.vector.reduce.add.v4f32(<4 x float> %vec)llvm.vector.shuffle: 对向量中的元素进行洗牌操作。
%result = call <4 x i32> @llvm.vector.shuffle.v4i32(<4 x i32> %vec1, <4 x i32> %vec2, <4 x i32> %mask)llvm.x86.sse2.*: 提供对x86 SSE2指令集的支持,如SIMD操作。
%result = call <2 x double> @llvm.x86.sse2.add.pd(<2 x double> %vec1, <2 x double> %vec2)
内部函数的生命周期
- LLVM IR 生成阶段
在前端编译器(如 Clang)将高级语言代码(如 C/C++)转换为 LLVM IR 时,内部函数被插入到 LLVM IR 中。例如,当代码中涉及到特定的操作,如内存复制、数学计算或原子操作,编译器可能会插入相应的内部函数,如 llvm.memcpy、llvm.sqrt 或 llvm.atomicrmw。
此时,内部函数仅仅以函数调用的形式存在于 LLVM IR 中,类似于其他普通函数调用。 - LLVM 中间优化阶段
在 LLVM 的中间优化过程中,内部函数和其他 IR 指令一起被处理。LLVM 的优化器可能识别和优化这些内部函数。例如:
- 常量传播:对于像 llvm.sqrt 这样的数学运算,如果操作数是常量,优化器可以在此阶段直接计算结果。
- 内联优化:某些内部函数可能会被展开为更简单的 IR 指令,尤其是那些可以直接映射为标准的 LLVM IR 指令的内部函数。
此阶段,内部函数仍然作为 LLVM IR 的一部分被表示。
- LLVM 后端生成阶段
在代码生成阶段,后端根据目标硬件架构将 LLVM IR 转换为具体的机器代码。在这一阶段,内部函数的处理方式取决于目标平台的硬件特性:
- 对于某些内部函数(如 llvm.sqrt、llvm.memcpy),后端会将它们映射为目标平台上的硬件指令,如 x86 上的 sqrtss 或 rep movsb 指令。
- 对于一些复杂的内部函数,后端可能会使用一系列指令来实现其功能,或者展开为更底层的操作。
在后端生成机器代码的过程中,内部函数会被转换或替换为具体的目标指令。至此,内部函数的生命周期结束。
元数据
元数据(Metadata) 是一种特殊的附加信息,不影响程序的功能执行,但能为编译器优化、调试和分析提供重要的辅助信息。元数据可以包含调试信息、代码优化提示、分析信息等。元数据不会在生成的目标代码中体现,也不会改变代码的行为。
元数据的基本结构使用 ! 符号表示。
- 元数据分为 独立元数据 和 附加元数据。
- 独立元数据:与指令或变量无直接关联的元数据。
- 附加元数据:绑定在指令或全局变量上,为它们提供额外的信息。
!0 = !{!“some information”} ; 定义一个元数据节点
1、元数据的语法
元数据的语法有多种形式,包括元数据节点、字符串、整型、浮点数等。常见的元数据语法如下:
元数据节点:
元数据可以表示为一个元数据节点,用花括号括起来,节点中的元素可以是常量、字符串、其他元数据节点等。
!0 = !{!"metadata example", i32 42, !1}
!1 = !{!"nested metadata", i1 true}
附加元数据:
元数据可以与指令关联,这样的元数据叫做附加元数据。它附加在 LLVM IR 的指令后,用元数据节点标记。
%result = add i32 %a, %b, !metadata_tag !0 ; 这个 add 指令带有元数据
在上面例子中,add 指令关联了元数据 !0,其中 !metadata_tag 是元数据的标签名。
元数据的类型
2、元数据的类型
LLVM IR 中的元数据主要有以下几种类型:
调试元数据(Debug Metadata)
调试元数据用于生成调试信息,通常包括源文件名、行号、变量类型等。这些信息不会影响程序的功能,但可以帮助调试工具(如 GDB 或 LLDB)还原源代码中的调试信息。
调试元数据包括以下类型:
- DILocation:存储指令的源代码位置信息,如行号和文件名。
- DISubprogram:描述函数的信息。
- DIType:存储类型信息。
- DIVariable:存储变量信息。
%1 = call i32 @some_function(), !dbg !12
!12 = !DILocation(line: 42, column: 13, scope: !13)
!13 = !DISubprogram(name: "some_function", ...)
这里的 !dbg 标记表示调用指令 %1 关联了调试信息,!12 描述了源代码中的行号、列号等。
优化提示元数据(Optimization Metadata)
优化提示元数据为编译器提供如何优化代码的提示,例如循环展开、向量化、分支预测等。
常见的优化元数据包括:
- !llvm.loop: 提供循环优化的信息。
- !llvm.mem.parallel_loop_access: 指示内存访问的并行性,提示编译器可以安全地对循环进行并行化。
优化元数据示例:
br label %loop, !llvm.loop !10
!10 = distinct !{!10, !11}
!11 = !{!"llvm.loop.unroll.full"}
这个例子中的 !llvm.loop.unroll.full 表示编译器可以完全展开这个循环。
分析元数据(Analysis Metadata)
分析元数据用于静态分析工具,它提供了额外的信息,以便工具能更好地分析代码。例如,!range 元数据可以为值提供范围信息,帮助分析工具推断变量的取值范围。
%result = load i32, i32* %ptr, !range !20
!20 = !{i32 0, i32 100} ; 表示 %result 的值在 0 到 100 之间
Type Metadata(类型元数据)
这类元数据描述的是类型信息,特别是在编译器优化过程中,元数据会为类型提供更多的上下文信息,帮助编译器作出优化决策。
TBAA Metadata(Type-Based Alias Analysis 元数据)
TBAA 元数据用于类型别名分析,帮助编译器了解不同内存位置是否可以别名。通过提供别名信息,编译器能够进行更激进的优化。
TBAA 元数据可以防止不必要的内存依赖检查,提升性能。
TBAA 元数据示例:
%0 = load i32, i32* %ptr, !tbaa !5
!5 = !{!"Simple C/C++ TBAA", !6, i64 0}
相关文章:
【编译器】-LLVMIR
概述 LLVM 是一种基于静态单赋值 (SSA) 的表示形式,提供类型安全、低级操作、灵活性以及干净地表示“所有”高级语言的能力。 LLVM IR 是一门低级语言,语法类似于汇编任何高级编程语言(如C)都可以用LLVM IR表示基于LLVM IR可以很…...
java面试场景问题
还在补充,这几天工作忙,闲了会把答案附上去,也欢迎各位大佬评论区讨论 1.不用分布式锁如何防重复提交 方法 1:基于唯一请求 ID(幂等 Token) 思路:前端生成 一个唯一的 requestId(…...
算法菜鸡备战3月2日传智杯省赛----0221
2209. 用地毯覆盖后的最少白色砖块 - 力扣(LeetCode) 力扣每日一题 class Solution { public:// 白色最少 黑色最多int minimumWhiteTiles(string floor, int numCarpets, int carpetLen) {int n floor.size();// 记忆化搜索vector memo(n 1, vector&…...
python pandas下载
pandas pandas:就是一个可以处理数据的 python 库 核心功能: 数据的清洗:处理丢失值,重复值数据分析:计算和统计信息,或分组汇总数据可视化:结合 图标库(Matplotlib)完成数据可视化…...
高斯牛顿法(GN)与列文伯格-马夸尔特方法在ORB-SLAM3中的应用
问题背景 高斯牛顿法(Gauss-Newton, GN)和列文伯格-马夸尔特方法(Levenburg-Marquadt, LM)是两种最常用的非线性优化方法,这两种方法在ORB-SLAM3系统中均有使用。 在ORB-SLAM3前端跟踪线程(Tracking)中,局…...
Python+Selenium+Pytest+POM自动化测试框架封装
🍅 点击文末小卡片 ,免费获取软件测试全套资料,资料在手,涨薪更快 1、测试框架简介 1)测试框架的优点 代码复用率高,如果不使用框架的话,代码会显得很冗余。可以组装日志、报告、邮件等一些高…...
猿大师中间件:网页直接内嵌本机EXE、OCX控件、ActiveX控件或桌面应用程序神器
猿大师中间件自从2019年发布以来,迄今为止不断迭代升级,给第三方提供了将自己的桌面程序和OCX控件支持直接内嵌到浏览器网页运行的赋能SDK开发包。 目前针对不同需求发布了三个成熟且商用的产品: 猿大师播放器:浏览器中直接原生…...
【Python】03-Python语法入门
文章目录 1、基本概念1.1、表达式1.2、语句1.3、程序(program)1.4、函数(function) 2、基本语法3、字面量与变量4、变量与标识符 1、基本概念 1.1、表达式 表达式就是一个类似于数学公式的东西,表达式一般仅用来计算一…...
C++,设计模式,【工厂方法模式】
文章目录 如何用汽车生产线理解工厂方法模式?一、传统生产方式的困境二、工厂方法模式解决方案三、模式应用场景四、模式优势分析五、现实应用启示✅C++,设计模式,【目录篇】 如何用汽车生产线理解工厂方法模式? 某个早晨,某车企CEO看着会议室里堆积如面的新车订单皱起眉…...
跟着 Lua 5.1 官方参考文档学习 Lua (5)
文章目录 2.10 – Garbage Collection2.10.1 – Garbage-Collection Metamethods2.10.2 – Weak Tables 2.10 – Garbage Collection Lua performs automatic memory management. This means that you have to worry neither about allocating memory for new objects nor abo…...
9.PG数据库层权限管理(pg系列课程)第2遍
一、PostgreSQL数据库属主 Postgres中的数据库属主属于创建者,只要有createdb的权限就可以创建数据库,数据库属主不一定拥有存放在该数据库中其它用户创建的对象的访问权限。数据库在创建后,允许public角色连接,即允许任何人连接…...
鸿蒙-canvas-画时钟
文章目录 前言准备分析组成部分数值计算过程 开始第一步 画圆环第二步 画格子第三步 画数字第四、五步 画指针&定时更新最后一步 前言 你在 Android 上能画出来的东西,在鸿蒙上画不出来? 画个时钟嘛,有啥难的? 你行你上&…...
【AI实践】阿里百炼文本对话Agent安卓版搭建
环境:安卓手机运行环境;WinsurfAI编程工具;阿里百炼提前创建Agent应用; 耗时:2小时; 1,新建安卓项目 完成文本输入,并将输入的文字显示出来。 2,安装SDK 参考文档 安…...
算法很美笔记(Java)——动态规划
解重叠子问题(当前解用到了以前求过的解) 形式:记忆型递归或递推(dp) 动态规划本质是递推,核心是找到状态转移的方式,也就是填excel表时的逻辑(填的方式),而…...
Jest单元测试
由于格式和图片解析问题,可前往 阅读原文 前端自动化测试在提高代码质量、减少错误、提高团队协作和加速交付流程方面发挥着重要作用。它是现代软件开发中不可或缺的一部分,可以帮助开发团队构建可靠、高质量的应用程序 单元测试(Unit Testi…...
《Stable Diffusion绘画完全指南:从入门到精通的Prompt设计艺术》-配套代码示例
第一章:模型加载与基础生成 1.1 基础模型加载 from diffusers import StableDiffusionPipeline import torch# 加载SD 1.5基础模型(FP32精度) pipe StableDiffusionPipeline.from_pretrained("runwayml/stable-diffusion-v1-5",…...
OnlyOffice:前端编辑器与后端API实现高效办公
OnlyOffice:前端编辑器与后端API实现高效办公 一、OnlyOffice概述二、前端编辑器:高效、灵活且易用1. 完善的编辑功能2. 实时协作支持3. 自动保存与版本管理4. 高度自定义的界面 三、后端API:管理文档、用户与权限1. 轻松集成与定制2. 实时协…...
springboot多实例部署时,@Scheduled注释的方法重复执行
问题:springboot多实例部署时,Scheduled注释的方法重复执行 在 Spring Boot 中要实现 Redis 的SET NX EX命令,可以借助 Spring Data Redis 来完成。SET NX EX命令用于在键不存在时设置键值对,并同时设置过期时间。 <dependen…...
coco格式
COCO(Common Objects in Context)格式是一种广泛用于图像识别和分割任务的数据格式,尤其是在目标检测、语义分割等任务中。COCO格式的核心包括以下几个部分: images: 包含图像的基本信息(如文件名、大小、ID等&#x…...
骶骨神经
骶骨肿瘤手术后遗症是什么_39健康网_癌症 [健康之路]匠心仁术(七) 勇闯禁区 骶骨肿瘤切除术...
Nacos学习(二)——继承Feign与Config中心
目录 一、集成Feign (一)基础用法 1.添加openfeign依赖 2. 开启openFeign注解扫描 3.创建ProviderService接口 4.修改ConsumerController (二)OpenFeign日志配置 (三)参数传递 1.参数传递的问题 2.参数传递的方式 2.1URL路径传参 2.2URL上拼接参数 2.3body传参 …...
计算机网络安全之一:网络安全概述
1.1 网络安全的内涵 随着计算机和网络技术的迅猛发展和广泛普及,越来越多的企业将经营的各种业务建立在Internet/Intranet环境中。于是,支持E-mail、文件共享、即时消息传送的消息和协作服务器成为当今商业社会中的极重要的IT基础设施。然而࿰…...
未来SLAM的研究方向和热点
SLAM(Simultaneous Localization and Mapping)是同时定位与地图构建的缩写,指的是机器人或设备在一个未知环境中一边进行自我定位,一边构建出环境的地图。SLAM广泛应用于机器人、自动驾驶、无人机等领域,涉及多个研究方…...
DuodooBMS源码解读之 purchase_change 模块
采购变更模块用户使用手册 一、模块概述 本扩展模块主要用于处理采购变更相关业务,包括采购变更单的创建、展示以及将采购变更信息导出为 Excel 文件等功能。以下将详细介绍该模块的具体使用方法。 二、模块功能及使用方法 (一)采购变更单…...
uniapp中引入Vant Weapp的保姆级教学(包含错误处理)
废话不多说,直接上方法,网上的教学好多都是错误的 1.安装vant weapp 在Hbuilder的终端,输入以下代码 npm install vant/weapp -S --production 2.新建wxcomponents文件夹 在项目的跟目录新建一个“wxcomponents’文件夹,与app.…...
Effective C++ 读书笔记(十二)
条款三十四:区分接口继承和实现继承 public继承由两部分组成:函数接口继承和函数实现继承。这两者的差异很像函数声明和函数定义之间的差异。 作为类的设计者,我们有时希望派生类只继承成员函数的接口(也就是函数声明࿰…...
【卡梅德生物】构建噬菌体文库与噬菌体展示文库构建服务新探索
在生命科学与生物技术快速发展的当下,抗体文库构建、构建噬菌体文库以及噬菌体展示文库构建服务在生物医药研发领域中占据着举足轻重的地位。它们不仅是基础研究的重要工具,更是推动抗体药物开发、疾病诊断技术进步的关键力量。 构建噬菌体文库是整个技…...
【JavaScript】《JavaScript高级程序设计 (第4版) 》笔记-Chapter19-表单脚本
十九、表单脚本 表单脚本 JavaScript 较早的一个用途是承担一部分服务器端表单处理的责任。虽然 Web 和 JavaScript 都已经发展了很多年,但 Web 表单的变化不是很大。由于不能直接使用表单解决问题,因此开发者不得不使用JavaScript 既做表单验证…...
C++STL容器之map
1.介绍 map是 C 标准模板库(STL)中的一个关联容器,用于存储键值对(key-value pairs)。map中的元素是按照键(key)进行排序的,并且每个键在容器中是唯一的。map通常基于红黑树…...
基于Nanopi duo2的WiFi智能摄像头
1.固件包烧录 https://wiki.friendlyelec.com/wiki/index.php/NanoPi_Duo2/zh#.E8.BF.9E.E6.8E.A5WiFi 固件包链接以及烧录工具都在上面链接中 烧录过程 使用读卡器将SD卡插入到电脑,然后打开烧录工具 2.通过串口工具连接板子使其连接WiFi 对应的串口工具,就是这个HyperT…...
