LLVM一些编程语法语义特性
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”。
通常,模块由全局值列表组成(其中函数和全局变量都是全局值)。全局值由指向内存位置的指针(在这种情况下,指向字符数组的指针和指向函数的指针)表示。
命名元数据是元数据的集合。元数据节点(但不是元数据字符串)是命名元数据的唯一有效操作数。
- 命名元数据表示为带有元数据前缀的字符串。元数据名称的规则与标识符相同,但不允许引用名称。"\xx"类型转义仍然有效,允许任何字符成为名称的一部分。
句法:
; 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关键字中的规范覆盖。此列表中给出了默认规格:
- E - big endian
- p:64:64:64 - 64-bit pointers with 64-bit alignment.
- p[n]:64:64:64 - Other address spaces are assumed to be the same as the default address space.
- S0 - natural stack alignment is unspecified
- i1:8:8 - i1 is 8-bit (byte) aligned
- i8:8:8 - i8 is 8-bit (byte) aligned
- i16:16:16 - i16 is 16-bit aligned
- i32:32:32 - i32 is 32-bit aligned
- i64:32:64 - i64 has ABI alignment of 32-bits but preferred alignment of 64-bits
- f16:16:16 - half is 16-bit aligned
- f32:32:32 - float is 32-bit aligned
- f64:64:64 - double is 64-bit aligned
- f128:128:128 - quad is 128-bit aligned
- v64:64:64 - 64-bit vector is 64-bit aligned
- v128:128:128 - 128-bit vector is 128-bit aligned
- a:0:64 - aggregates are 64-bit aligned
使用列表指令,对每个使用列表的内存顺序进行编码,允许重新创建顺序。<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 中存在错误。
所有目标通常都支持一些约束代码:
- r:目标通用寄存器类中的寄存器。
- m: 一个内存地址操作数。支持什么寻址模式是特定于目标的,典型的例子是寄存器,或寄存器 + 寄存器偏移量,或寄存器 + 立即偏移量(某些特定于目标的大小)。
- i: 整数常量(特定于目标的宽度)。允许简单的立即数或可重定位的值。
- n: 一个整数常量——不包括可重定位的值。
- s: 一个整数常量,但只允许可重定位的值。
- X: 允许任何类型的操作数,没有任何约束。通常用于为 asm 分支或调用传递标签。
- {register-name}: 需要完全命名的物理寄存器。
其它约束是特定于目标的:
AArch64:
- z: 一个立即数 0。输出WZR或XZR,视情况而定。
- I: 对ADDorSUB指令有效的立即数,即 0 到 4095,可选择移位 12。
- J: 一个立即数,当取反时,对ADDor SUB指令有效,即 -1 到 -4095,可选左移 12。
- K:一个直接整数,它是有效的逻辑指令等的“位掩码即时32” AND,EOR或ORR与32位寄存器。
- L:一个直接整数,它是有效的逻辑指令等的“位掩码即时64” AND,EOR或ORR在64位寄存器。
- M: 与MOV32 位寄存器上的程序集别名一起使用的立即数。这是 的超集K:除了位掩码immediate 之外,还允许可以使用单个MOVZ或MOVL指令加载的立即整数 。
- N: 与MOV64 位寄存器上的程序集别名一起使用的立即数。这是 的超集L。
- Q:内存地址操作数必须在单个寄存器中(无偏移)。(但是,LLVM 目前也为m约束执行此操作。)
- r:一个 32 或 64 位整数寄存器(W* 或 X*)。
- w:一个 32、64 或 128 位浮点/SIMD 寄存器。
- x: 一个较低的 128 位浮点/SIMD 寄存器 ( V0to V15)。
AMDGPU:
- r: 一个 32 位或 64 位整数寄存器。
- [0-9]v:32 位 VGPR 寄存器,编号 0-9。
- [0-9]s: 32 位 SGPR 寄存器,编号 0-9。
所有 ARM 模式:
- Q, Um, Un, Uq, Us, Ut, Uv, Uy: 内存地址操作数。目前与操作数相同m。
ARM 和 ARM 的 Thumb2 模式:
- j: 0 到 65535 之间的立即数(对 有效MOVW)
- I: 一个对数据处理指令有效的立即数。
- J: -4095 到 4095 之间的立即数。
- K: 一个立即数,其按位反转对数据处理指令有效。(可与模板修饰符“ B”一起使用以打印反转值)。
- L: 一个立即数,其取反对数据处理指令有效。(可与模板修饰符“ n”一起使用以打印取反值)。
- M: 2 的幂或 0 到 32 之间的整数。
- N: 无效的立即约束。
- O: 无效的立即约束。
- r: 一个通用的 32 位整数寄存器 ( r0-r15)。
- l: 在 Thumb2 模式下,低 32 位 GPR 寄存器 ( r0-r7)。在 ARM 模式下,同r.
- h: 在 Thumb2 模式下,高 32 位 GPR 寄存器 ( r8-r15)。在 ARM 模式下,无效。
- w:一个 32、64 或 128 位浮点/SIMD 寄存器:s0-s31、 d0-d31、 或q0-q15。
- x:一个 32、64 或 128 位浮点/SIMD 寄存器:s0-s15、 d0-d7、 或q0-q3。
- t:浮点/SIMD 寄存器,仅支持 32 位值: s0-s31。
ARM 的 Thumb1 模式:
- I: 0 到 255 之间的立即数。
- J: -255 和 -1 之间的立即数。
- K: 0 到 255 之间的立即数,可选择左移一定量。
- L: -7 到 7 之间的立即数。
- M: 0 到 1020 之间的 4 的倍数的立即数。
- N: 0 到 31 之间的立即数。
- O: 一个介于 -508 和 508 之间的 4 的倍数的立即数。
- r: 一个低 32 位 GPR 寄存器 ( r0-r7)。
- l: 一个低 32 位 GPR 寄存器 ( r0-r7)。
- h: 一个高 GPR 寄存器 ( r0-r7)。
- w:一个 32、64 或 128 位浮点/SIMD 寄存器:s0-s31、 d0-d31、 或q0-q15。
- x:一个 32、64 或 128 位浮点/SIMD 寄存器:s0-s15、 d0-d7、 或q0-q3。
- t:浮点/SIMD 寄存器,仅支持 32 位值: s0-s31。
Hexagon:
- o, v: 一个内存地址操作数,目前与约束相同m。
- r: 32 位或 64 位寄存器。
MSP430:
- r: 8 位或 16 位寄存器。
MIPS:
- I:立即有符号的 16 位整数。
- J:立即整数零。
- K: 立即数无符号 16 位整数。
- L:立即数 32 位整数,其中低 16 位为 0。
- N: -65535 和 -1 之间的立即数。
- O:立即有符号的 15 位整数。
- P: 1 到 65535 之间的一个立即数。
- m: 一个内存地址操作数。在 MIPS-SE 模式下,允许基地址寄存器加上 16 位立即偏移量。在 MIPS 模式下,只是一个基址寄存器。
- R: 一个内存地址操作数。在 MIPS-SE 模式下,允许基地址寄存器加上 9 位有符号偏移量。在 MIPS 模式下,与约束相同 m。
- ZC:一个存储器地址操作数,适用于使用pref,ll或 sc在给定的子目标指令(细节有所不同)。
- r, d, y: 32 或 64 位 GPR 寄存器。
- f:32 或 64 位 FPU 寄存器 ( F0-F31),或 128 位 MSA 寄存器 ( W0-W31)。对于 MSA 寄存器,建议使用w 参数修饰符以与 GCC 兼容。
- c:适用于间接跳转的 32 位或 64 位 GPR 寄存器(始终 25)。
- l:lo寄存器,32 位或 64 位。
- x: 无效的。
NVPTX:
- b: 1 位整数寄存器。
- c或h:一个 16 位整数寄存器。
- r: 一个 32 位整数寄存器。
- l或N:一个 64 位整数寄存器。
- f: 一个 32 位浮点寄存器。
- d: 一个 64 位浮点寄存器。
PowerPC:
- I:立即有符号的 16 位整数。
- J:立即数无符号 16 位整数,左移 16 位。
- K: 立即数无符号 16 位整数。
- L: 一个有符号的立即数 16 位整数,左移 16 位。
- M: 一个大于 31 的立即数。
- N: 是 2 的精确幂的立即数。
- O: 立即数整数常量 0。
- P: 一个立即整数常量,其否定是一个有符号的 16 位常量。
- es, o, Q, Z, Zy: 一个内存地址操作数,目前与m.
- r: 一个 32 位或 64 位整数寄存器。
- b: 一个 32 位或 64 位整数寄存器,不包括R0(即 :)R1-R31。
- f:一个 32 或 64 位浮点寄存器 ( F0-F31),或者当启用 QPX 时,一个 128 或 256 位 QPX 寄存器 ( Q0-Q31; 别名F寄存器)。
- v: 对于or类型,当启用 QPX 时,为 128 或 256 位 QPX 寄存器 ( ),否则为 128 位 altivec 矢量寄存器 ( )。4
- y: 条件寄存器 ( CR0-CR7)。
- wc: CR 寄存器中的单个 CR 位。
- wa, wd, wf: 任何 128 位 VSX 向量寄存器,来自完整的 VSX 寄存器集(重叠浮点和向量寄存器文件)。
- ws:来自完整 VSX 寄存器组的 32 位或 64 位浮点寄存器。
sparc:
- I:立即数 13 位有符号整数。
- r: 一个 32 位整数寄存器。
- f:SparcV8 上的任何浮点寄存器,或 SparcV9 上寄存器“低”半部分的浮点寄存器。
- e: 任何浮点寄存器。(与fSparcV8相同。)
SystemZ:
- I: 立即数无符号 8 位整数。
- J: 立即数无符号 12 位整数。
- K:立即有符号的 16 位整数。
- L:立即有符号的 20 位整数。
- M: 一个立即数 0x7ffffffff。
- Q:具有基地址和 12 位立即数无符号位移的内存地址操作数。
- R:具有基地址、12 位立即数无符号位移和索引寄存器的内存地址操作数。
- S:具有基地址和 20 位立即有符号位移的内存地址操作数。
- T:具有基地址、20 位立即符号位移和索引寄存器的内存地址操作数。
- r或d:32、64 或 128 位整数寄存器。
- a:一个 32、64 或 128 位整数地址寄存器(不包括 R0,它在地址上下文中计算为零)。
- h: 64 位数据寄存器高位部分的 32 位值(LLVM 特定)
- f:一个 32、64 或 128 位浮点寄存器。
X86:
- I: 0 到 31 之间的立即数。
- J: 0 到 64 之间的立即数。
- K: 一个有符号的立即数 8 位整数。
- L:立即数,0xff 或 0xffff 或(仅在 64 位模式下)0xffffffff。
- M: 0 到 3 之间的立即数。
- N: 立即数无符号 8 位整数。
- O: 0 到 127 之间的立即数。
- e:立即数 32 位有符号整数。
- Z:立即数 32 位无符号整数。
- o, v: 目前与m,一样对待。
- q:一个 8、16、32 或 64 位寄存器,可作为 8 位 l整数寄存器访问。在X86-32,这是a,b,c,和d 寄存器,以及X86-64,它是所有的整数寄存器。
- Q:一个 8、16、32 或 64 位寄存器,可作为 8 位 h整数寄存器访问。这是a,b,c,和d寄存器。
- r或l:一个 8、16、32 或 64 位整数寄存器。
- R:一个 8、16、32 或 64 位“传统”整数寄存器——自 i386 以来就已经存在,无需 REX 前缀即可访问。
- f:一个 32、64 或 80 位的 '387 FPU 堆栈伪寄存器。
- y: 64 位 MMX 寄存器,如果启用 MMX。
- x:如果启用了 SSE:SSE 寄存器中的 32 或 64 位标量操作数,或 128 位向量操作数。如果也启用了 AVX,也可以是 AVX 寄存器中的 256 位向量操作数。如果也启用了 AVX-512,也可以是 AVX512 寄存器中的 512 位向量操作数,否则报错。
- Y: 与 相同x,如果启用了SSE2,否则会出错。
- A: 特殊情况:首先为单个操作数分配 EAX,然后是 EDX(在 32 位模式下,64 位整数操作数将被拆分为两个寄存器)。不建议使用此约束,因为在 64 位模式下,64 位操作数将仅分配给 RAX – 如果需要两个 32 位操作数,最好自己拆分,然后再将其传递给asm 语句。
Core:
- r: 一个 32 位整数寄存器。
在 asm 模板字符串中,可以在操作数引用上使用修饰符,例如“ ${0:n}”。
通常,修饰符的行为方式,与在 GCC 中的行为方式相同。LLVM 的支持通常是在“按需”基础上实现的,支持 GCC 支持的 C 内联汇编代码。LLVM 和 GCC 之间的行为不匹配,可能表明 LLVM 中存在错误。
目标独立:
- c: 打印一个不加修饰的直接整数常量,没有特定于目标的直接标点符号(例如没有$前缀)。
- n: 否定并打印未经修饰的立即整数常量,没有特定于目标的立即标点符号(例如,没有$前缀)。
- l: 打印为未修饰的标签,没有特定于目标的标签标点符号(例如,没有$前缀)。
AArch64:
- w: 使用w*名称而不是x*名称打印 GPR 寄存器。例如,代替x30,打印w30。
- x:打印带有x*名称的 GPR 寄存器。(无论如何,这是默认设置)。
- b, h, s, d, q: 打印带有b*, h*, s*, d*, 或q*name的浮点/SIMD 寄存器 ,而不是默认的 v*。
AMDGPU:
- r: 没有效果。
ARM:
- a: 将操作数打印为地址(带有[和]包围寄存器)。
- P: 没有效果。
- q: 没有效果。
- y: 将 VFP 单精度寄存器打印为索引双精度值(例如打印为d4[1]而不是s9)
- B: 按位反转并打印一个没有# 前缀的立即整数常量。
- L: 打印立即整数常量的低 16 位。
- M: 打印为适合 ldm/stm 的寄存器集。还会打印 指定一个 (!) 之后的所有寄存器操作数,因此请谨慎使用。
- Q: 打印一个寄存器对的低位寄存器,或两个寄存器操作数的低位寄存器。
- R: 打印一个寄存器对的高位寄存器,或两个寄存器操作数的高位寄存器。
- H: 打印寄存器对的第二个寄存器。(在大端系统上, H等价于Q,在小端系统上,H等价于R。)
- e: 打印 NEON quad 寄存器的低位双字寄存器。
- f: 打印 NEON quad 寄存器的高位双字寄存器。
- m: 打印不带[and] 修饰的内存操作数的基址寄存器。
Hexagon:
- L: 打印双寄存器操作数的第二个寄存器。要求它已经连续分配到第一个。
- I: 如果操作数是整数常量,则打印字母 'i',否则不打印。用于打印“addi”与“add”指令。
MSP430:
没有额外的修饰符。
MIPS:
- X: 以十六进制打印一个立即数
- x: 将立即数的低 16 位打印为十六进制。
- d: 将立即整数打印为十进制。
- m: 减一并以十进制形式打印一个立即数。
- z: 如果立即数为零,则打印 $0,否则正常打印。
- L: 打印二寄存器操作数的低位寄存器,或打印双字内存操作数的低位字地址。
- M: 打印二寄存器操作数的高位寄存器,或打印双字内存操作数的高位字地址。
- D: 打印双寄存器操作数的第二个寄存器,或打印双字内存操作数的第二个字。(在大端系统上,D等价于L,在小端系统上,D等价于 M。)
- w: 没有效果。提供与 GCC 的兼容性,它需要此修饰符才能W0-W31使用f 约束打印 MSA 寄存器 ( ) 。
NVPTX:
- r: 没有效果。
PowerPC:
- L: 打印双寄存器操作数的第二个寄存器。要求它已经连续分配到第一个。
- I: 如果操作数是整数常量,则打印字母 'i',否则不打印。用于打印“addi”与“add”指令。
- y: 对于内存操作数,打印两寄存器 X 格式指令的格式化程序。(目前总是打印r0,OPERAND)。
- U: 如果内存操作数是更新形式,则打印 'u',否则不打印。(注意:LLVM 不支持更新表单,因此当前将始终不打印任何内容)
- X: 如果内存操作数是索引形式,则打印“x”。(注意:LLVM 不支持索引形式,因此目前将始终不打印任何内容)
Sparc:
- r: 没有效果。
SystemZ:
SystemZ 仅实现n,不支持任何其它与目标无关的修饰符。
X86:
- c: 打印未修饰的整数或符号名称。(后者是此通常与目标无关的修饰符的特定于目标的行为)。
- A: 打印*前面带有“ ”的寄存器名称。
- b: 打印一个 8 位寄存器名称(例如al);在内存操作数上什么都不做。
- h: 打印高8位寄存器名(如ah);在内存操作数上什么都不做。
- w: 打印 16 位寄存器名称(例如ax);在内存操作数上什么都不做。
- k: 打印 32 位寄存器名称(例如eax);在内存操作数上什么都不做。
- q: 打印 64 位寄存器名称(例如rax),如果 64 位寄存器可用,否则打印32 位寄存器名称;在内存操作数上什么都不做。
- n: 取反并打印一个未修饰的整数,或者,对于非立即数的操作数(例如可重定位符号表达式),在操作数前打印一个“-”。(可重定位符号表达式的行为是此通常与目标无关的修饰符的特定于目标的行为)
- H:打印具有额外偏移量 +8 的内存引用。
- P:打印内存引用或操作数以用作调用指令的参数。(例如 omit (rip),即使它是 PC 相关的。)
参考链接:
https://releases.llvm.org/6.0.0/docs/LangRef.html#module-structure