LLVM一些编程语法语义特性
High Level Structure
Module Structure
LLVM 程序由Module's组成,每个 's 是输入程序的一个翻译单元。每个模块由函数、全局变量和符号表条目组成。模块可以与 LLVM 链接器组合在一起,后者合并函数(全局变量)定义、解析前向声明,合并符号表条目。这是“hello world”模块的示例:
; Declare the string constant as a global constant.
@.str = private unnamed_addr constant [13 x i8] c"hello world\0A\00"
; External declaration of the puts function
declare i32 @puts(i8* nocapture) nounwind
; Definition of main function
define i32 @main() { ; i32()*
; Convert [13 x i8]* to i8*...
%cast210 = getelementptr [13 x i8], [13 x i8]* @.str, i64 0, i64 0
; Call puts function to write out the string to stdout.
call i32 @puts(i8* %cast210)
ret i32 0
}
; Named metadata
!0 = !{i32 42, null, !"string"}
!foo = !{!0}
这个例子是由高达全局变量命名为“ .str”,在一个外部声明“ puts”函数, 函数定义为“ main”, 命名为元数据“ foo”。
通常,模块由全局值列表组成(其中函数和全局变量都是全局值)。全局值由指向内存位置的指针(在这种情况下,指向字符数组的指针和指向函数的指针)表示。
命名元数据是元数据的集合。元数据节点(但不是元数据字符串)是命名元数据的唯一有效操作数。
句法:
; Some unnamed metadata nodes, which are referenced by the named metadata.
!0 = !{!"zero"}
!1 = !{!"one"}
!2 = !{!"two"}
; A named metadata.
!name = !{!0, !1, !2}
函数类型的返回类型和每个参数,可能有一组与之关联的参数属性。参数属性用于传达有关函数结果,或参数的附加信息。参数属性被认为是函数的一部分,不是函数类型,具有不同参数属性的函数,可以具有相同的函数类型。
参数属性是遵循指定类型的简单关键字。如果需要多个参数属性,空格分隔。例如:
declare i32 @printf(i8* noalias nocapture, ...)
declare i32 @atoi(i8 zeroext)
declare signext i8 @returns_signed_char()
前缀数据是与函数关联的数据,代码生成器将在函数入口点之前立即发出数据。允许前端将特定于语言的运行时元数据与特定函数相关联,通过函数指针可用,允许调用函数指针。
要访问给定函数的数据,程序可以将函数指针位转换为指向常量类型的指针,取消引用索引 -1。这意味着 IR 符号刚好越过前缀数据的末尾。例如,一个用单i32个,注释的函数为例
define void @f() prefix i32 123 { ... }
前缀数据可以引用
%0 = bitcast void* () @f to i32*
%a = getelementptr inbounds i32, i32* %0, i32 -1
%b = load i32, i32* %a
define void @f() prologue i8 144 { ... }
prologue属性允许在函数体之前,插入任意代码(编码为字节)。用于启用功能热修补和检测。
为了维护普通函数调用的语义,序言数据必须具有特定的格式。具体来说,必须以一个字节序列开始,这些字节序列解码为一系列机器指令,对模块的目标有效,这些指令将控制转移到序言数据之后的点,不执行任何可见的操作。允许内联和通道推理函数定义的语义,无需推理序言数据。显然,这使得序言数据的格式高度依赖于目标。
x86 架构的有效序言数据的一个简单示例,对指令进行编码:i8
144nop
define void @f() prologue i8 144 { ... }
通常可以通过编码,跳过元数据的相对分支指令,形成序言数据,如x86_64架构的有效序言数据示例,前两个字节编码:jmp
.+10
%0 = type <{ i8, i8, i8* }>
define void @f() prologue %0 <{ i8 235, i8 8, i8* @md}> { ... }
; Target-independent attributes:
attributes #0 = { alwaysinline alignstack=4 }
; Target-dependent attributes:
attributes #1 = { "no-sse" }
; Function @f has attributes: alwaysinline, alignstack=4, and "no-sse".
define void @f() #0 #1 { ... }
属性组是由 IR 内的对象引用的属性组。对于保持.ll文件可读很重要,因为许多函数将使用相同的属性集。在.ll文件对应于单个.c文件的退化情况下 ,单个属性组将捕获用于构建该文件的重要命令行标志。
属性组是模块级对象。要使用属性组,对象引用属性组的 ID(例如#37)。一个对象可能引用多个属性组。在这种情况下,来自不同组的属性将合并。
下面是一个函数的属性组示例,该函数应该始终内联,堆栈对齐为 4,不应使用 SSE 指令:
define void @f() noinline { ... }
define void @f() alwaysinline { ... }
define void @f() alwaysinline optsize { ... }
define void @f() optsize { ... }
去优化操作数包的特点是"deopt" 操作数包标签。这些操作数包代表了所连接的调用站点的替代“安全”延续,可以由合适的运行时使用,取消优化指定调用站点的编译帧。最多可以有一个"deopt"操作数束,附加到调用站点。去优化的确切细节超出了语言参考的范围,通常涉及将编译帧重写为一组解释帧。
从编译器的角度来看,去优化操作数包,所连接的调用点,至少连接到readonly。通读了所有指针类型的操作数(即使没有以其它方式转义)和整个可见堆。去优化操作数包不捕获操作数,除非在去优化期间,在这种情况下控制,不会返回到编译帧。
内联器知道如何通过具有去优化操作数包的调用进行内联。就像通过普通调用站点内联涉及组合正常和异常延续一样,通过具有去优化操作数包的调用站点,内联需要适当地组合“安全”去优化延续。内联程序通过将父级的去优化延续,添加到内联正文中的每个去优化延续之前,做到这一点。例如内联@f到@g在下面的例子中
define void @f() {
call void @x() ;; no deopt state
call void @y() [ "deopt"(i32 10) ]
call void @y() [ "deopt"(i32 10), "unknown"(i8* null) ]
ret void
}
define void @g() {
call void @f() [ "deopt"(i32 20) ]
ret void
}
导致
define void @g() {
call void @x() ;; still no deopt state
call void @y() [ "deopt"(i32 20, i32 10) ]
call void @y() [ "deopt"(i32 20, i32 10), "unknown"(i8* null) ]
ret void
}
在每个需要 a 的规范上<abi>:<pref>,指定 <pref>对齐方式是可选的。如果省略,前面的应该省略,并且<pref>等于<abi>。
在为给定目标构建数据布局时,LLVM 从一组默认规范开始,然后(可能)被datalayout关键字中的规范覆盖。此列表中给出了默认规格:
使用列表指令,对每个使用列表的内存顺序进行编码,允许重新创建顺序。<order-indexes>是分配给引用值的使用的索引的逗号分隔列表。引用值的使用列表,立即按这些索引排序。
使用列表指令,可能出现在函数作用域,或全局作用域。不是指令,对 IR 的语义没有影响。当处于函数作用域时,必须出现在最终基本块的终止符之后。
如果基本块的地址,通过blockaddress()表达式获取, uselistorder_bb则可用于从其函数范围之外重新排序使用列表。
Syntax: |
uselistorder <ty> <value>, { <order-indexes> }
uselistorder_bb @function, %block { <order-indexes> }
Examples: |
define void @foo(i32 %arg1, i32 %arg2) {
entry:
; ... instructions ...
bb:
; ... instructions ...
; At function scope.
uselistorder i32 %arg1, { 1, 0, 2 }
uselistorder label %bb, { 1, 0 }
}
; At global scope.
uselistorder i32* @global, { 1, 2, 0 }
uselistorder i32 7, { 1, 0 }
uselistorder i32 (i32) @bar, { 1, 0 }
uselistorder_bb @foo, %bb, { 5, 1, 3, 2, 0, 4 }
函数类型
概述: |
函数类型可以被认为是一个函数签名。由一个返回类型和一个形参类型列表组成。函数类型的返回类型是 void 类型,或第一类类型——标签和元数据类型除外。
句法: |
<returntype> (<parameter list>)
...其中 ' ' 是逗号分隔的类型说明符列表。参数列表可以包括一个 type ,表明该函数采用可变数量的参数。可变参数函数可以使用可变参数处理内部函数访问参数。' ' 是除标签和元数据之外的任何类型。<parameter 示例
整数将占用的位数由N 值指定。 例示例
浮点类型
指针类型
指针类型用于指定内存位置。指针通常用于引用内存中的对象。 指针类型可能有一个可选的地址空间属性,用于定义指向对象所在的编号地址空间。默认地址空间是数字零。非零地址空间的语义是特定于目标的。 请注意,LLVM 不允许指向 void ( void*) 的指针,也不允许指向标签 ( label*) 的指针。使用i8*来代替。
<类型> *
矢量类型¶
向量类型是表示元素向量的简单派生类型。当使用单个指令 (SIMD) ,并行操作多个原始数据时,将使用向量类型。向量类型需要大小(元素数量)和底层原始数据类型。向量类型被认为是一类的。 < <# elements> x <elementtype> > 元素个数是一个大于0的常量整数值;elementtype 可以是任何整数、浮点数或指针类型。不允许大小为零的向量。
数数组类型
数组类型是一种非常简单的派生类型,在内存中按顺序排列元素。数组类型需要大小(元素数)和基础数据类型。
[<# elements> x <elementtype>] 元素的数量是一个常量整数值;elementtype可以是具有大小的任何类型。
多维数组的一些示例:
对超出静态类型所隐含的数组末尾的索引,没有限制(尽管在某些情况下对超出已分配对象范围的索引有限制)。在具有零长度数组类型的 LLVM 中,实现一维“可变大小数组”寻址。例如,LLVM 中“pascal 样式数组”的实现,可以使用类型“ ”。{ 结构类型
结构类型用于表示内存中数据成员的集合。结构的元素可以是具有大小的任何类型。 通过使用“ ”指令,获取指向字段的指针,可以使用“ load”和“ store”访问内存中的结构getelementptr。使用“ extractvalue”和“ insertvalue”指令,访问寄存器中的结构。 结构可以选择是“打包”结构,这表明结构的对齐是一个字节,元素之间没有填充。在非压缩结构中,字段类型之间的填充,按照模块中 DataLayout 字符串的定义插入,这是匹配底层代码生成器所期望的。 结构可以是“文字”或“已知”。文字结构是与其它类型(例如)内联定义的,标识的类型总是在顶层定义一个名称。文字类型因内容而唯一,永远不会是递归或不透明的,无法编写。已知类型可以是递归的,可以是不透明的,永远不会是唯一的。{i32,
% T1 = type { <类型 列表> } ; 已知的 正常 结构体 类型 % T2 = type < { <类型 列表> } > ; 已知的 压缩 结构 类型
不透明结构类型
不透明结构类型,用于表示没有指定主体的命名结构类型。对应于(例如)前向声明结构的 C 概念。
% X = 类型 不透明 % 52 = 类型 不透明
全局变量和函数地址全局变量和 函数的地址总是隐式有效(链接时)常量。这些常量在使用全局标识符时显式引用,始终具有 指针类型。例如,以下是一个合法的 LLVM 文件: @X = global i32 17 @Y = global i32 42 @Z = global [2 x i32*] [ i32* @X, i32* @Y ] 未定义值字符串 ' undef' 可用于任何需要常量的地方,指示该值的用户,可能会收到未指定的位模式。未定义的值可以是任何类型(除了“ label”或“ void”),可以在任何允许常量的地方使用。 未定义值很有用,向编译器表明无论使用什么值,程序都是定义良好的。这给了编译器更多的优化自由。以下是一些有效(在伪 IR 中)转换的示例: %A = add %X, undef %B = sub %X, undef %C = xor %X, undef Safe: %A = undef %B = undef %C = undef 这是安全的,因为所有输出位都受 undef 位影响。任何输出位都可以有0,,1,具体取决于输入位。 %A = or %X, undef %B = and %X, undef Safe: %A = -1 %B = 0 Safe: %A = %X ;; By choosing undef as 0 %B = %X ;; By choosing undef as -1 Unsafe: %A = undef %B = undef 这些逻辑运算的位,并不总是受输入影响。例如,如果%X有一个零位,那么and对于该位' ' 操作的输出,将始终为零,无论 ' undef'的相应位是什么。因此,优化或假设“ and”的结果 “ undef”是不安全的。但是,可以安全地假设 ' undef' 的所有位,都可以为 0,将 ' and'优化为 0。同样,可以安全地假设 ' undef' 操作数的所有位or,都可以设置,允许将“ or”设置为 -1。 %A = select undef, %X, %Y %B = select undef, 42, %Y %C = select %X, %Y, undef Safe: %A = %X (or %Y) %B = 42 (or %Y) %C = %Y Unsafe: %A = undef %B = undef %C = undef这组示例表明,未定义的“ select”(和条件分支)条件,可以采用任何一种方式,必须来自两个操作数之一。在%A例子中,如果%X和%Y是两个已知具有明显的低位,那么%A就必须有一个清除低位。但是,在%C示例中,优化器可以假设“ undef”操作数,可以与 %Y相同,从而select可以消除整个“ ”。 %A = xor undef, undef %B = undef %C = xor %B, %B %D = undef %E = icmp slt %D, 4 %F = icmp gte %D, 4 Safe: %A = undef %B = undef %C = undef %D = undef %E = undef %F = undef 此示例指出两个“ undef”操作数不一定相同。可能会让人们感到惊讶(并且也匹配 C 语义),假设“ X^X”始终为零,即使 X是未定义的。出于多种原因,情况并非如此,但简短的回答是undef“变量”,可以在 “有效范围”内,任意更改值。因为变量实际上没有有效范围。相反,该值是在需要时,从恰好在附近的任意寄存器逻辑读取的,因此该值不一定随时间保持一致。事实上,%A与 %C需要具有相同的语义,或核心LLVM概念,也不会执行“替换所有用途”。 %A = fdiv undef, %X %B = fdiv %X, undef Safe: %A = undef b: unreachable 这些示例显示了未定义值 和未定义行为之间的关键区别。undef允许未定义的值(如“ ”)具有任意位模式。这意味着%A 操作可以常量折叠为“ undef”,因为“ undef”可能是 SNaN, fdiv(当前)未在 SNaN 上定义。在第二个例子中,可以做出更激进的假设:因为undef允许是任意值,可以假设可能为零。由于除以零具有未定义的行为,可以假设该操作根本不执行。删除除法和之后的所有代码。因为未定义的操作“不可能发生”,优化器可以假设发生在死代码中。 a: store undef -> %X b: store %X -> undef Safe: a: <deleted> b: unreachable 危险值危险值类似于undef 值,也代表了这样一个事实,即不能引起副作用的指令,或常量表达式,仍然检测到导致未定义行为的条件。 目前没有办法在 IR 中表示危险值;当通过操作,如产生只存在附加与nsw标记。 危险值行为是根据值依赖定义的:
危险值与undef值具有相同的行为,附加效果是任何依赖 危险值的指令,都具有未定义的行为。 例子: |
|
entry:
%poison = sub nuw i32 0, 1 ; Results in a poison value.
%still_poison = and i32 %poison, 0 ; 0, but also poison.
%poison_yet_again = getelementptr i32, i32* @h, i32 %still_poison
store i32 0, i32* %poison_yet_again ; memory at @h[0] is poisoned
store i32 %poison, i32* @g ; Poison value stored to memory.
%poison2 = load i32, i32* @g ; Poison value loaded back from memory.
store volatile i32 %poison, i32* @g ; External observation; undefined behavior.
%narrowaddr = bitcast i32* @g to i16*
%wideaddr = bitcast i32* @g to i64*
%poison3 = load i16, i16* %narrowaddr ; Returns a poison value.
%poison4 = load i64, i64* %wideaddr ; Returns a poison value.
%cmp = icmp slt i32 %poison, 0 ; Returns a poison value.
br i1 %cmp, label %true, label %end ; Branch to either destination.
true:
store volatile i32 0, i32* @g ; This is control-dependent on %cmp, so
; it has undefined behavior.
br label %end
end:
%p = phi i32 [ 0, %entry ], [ 1, %true ]
; Both edges into this PHI are
; control-dependent on %cmp, so this
; always results in a poison value.
store volatile i32 0, i32* @g ; This would depend on the store in %true
; if %cmp is true, or the store in %entry
; otherwise, so this is undefined behavior.
br i1 %cmp, label %second_true, label %second_end
; The same branch again, but this time the
; true block doesn't have side effects.
second_true:
; No side effects!
ret void
second_end:
store volatile i32 0, i32* @g ; This time, the instruction always depends
; on the store in %end. Also, it is
; control-equivalent to %end, so this is
; well-defined (ignoring earlier undefined
; behavior in this example).
通常,约束代码的行为方式,与在 GCC 中的行为方式相同。LLVM 的支持通常是在“按需”基础上实现的,支持 GCC 支持的 C 内联汇编代码。LLVM 和 GCC 之间的行为不匹配,表明 LLVM 中存在错误。
所有目标通常都支持一些约束代码:
其它约束是特定于目标的:
AArch64:
AMDGPU:
所有 ARM 模式:
ARM 和 ARM 的 Thumb2 模式:
ARM 的 Thumb1 模式:
Hexagon:
MSP430:
MIPS:
NVPTX:
PowerPC:
x
f324
x
f64Q0-Q31V0-V31sparc:
SystemZ:
X86:
Core:
在 asm 模板字符串中,可以在操作数引用上使用修饰符,例如“ ${0:n}”。
通常,修饰符的行为方式,与在 GCC 中的行为方式相同。LLVM 的支持通常是在“按需”基础上实现的,支持 GCC 支持的 C 内联汇编代码。LLVM 和 GCC 之间的行为不匹配,可能表明 LLVM 中存在错误。
目标独立:
AArch64:
AMDGPU:
ARM:
Hexagon:
MSP430:
没有额外的修饰符。
MIPS:
NVPTX:
PowerPC:
Sparc:
SystemZ:
SystemZ 仅实现n,不支持任何其它与目标无关的修饰符。
X86:
参考链接:
https://releases.llvm.org/6.0.0/docs/LangRef.html#module-structure