LLVM IR 内嵌汇编表达式

LLVM 通过使用特殊值支持内联汇编表达式(与模块级内联汇编相反)。该值表示内联汇编程序作为模板字符串(包含要发出的指令),操作数约束列表(存储为字符串),指示内联asm表达式是否具有副作用的标志以及指示是否包含asm的函数需要保守地调整堆栈。

模板字符串支持使用$后跟一个数字的参数替换,以指示由约束字符串指定的给定寄存器/内存位置的替换。${NUM:MODIFIER}也可以使用,其中MODIFIER是如何打印操作数的特定于目标的注释(请参阅Asm模板参数修饰符)。

字符$可以在模板中使用“$$”。要在输出中包含其他特殊字符,可以使用通常的“\XX”转义符,就像在其他字符串中一样。请注意,在模板替换之后,生成的汇编字符串将由LLVM的集成汇编器进行分析,除非它被禁用
– 即使发出.s文件 – 也必须包含LLVM已知的汇编语法。

LLVM还支持一些有用的内联汇编代码:

  • ${:uid}:扩展为这个内联汇编blob唯一的十进制整数。在声明本地标签时,这种替换很有用。许多标准的编译器优化(如内联)可能会复制内联asm
    blob。添加blob唯一标识符可确保这两个标签在装配过程中不会发生冲突。这用于实现GCC的%=特殊格式字符串
  • ${:comment}:扩展为当前目标的汇编方言的注释字符。这通常是#,但很多指标使用其他字符串,例如;//!
  • ${:private}:扩展为汇编器专用标签前缀。带有此前缀的标签不会出现在组装对象的符号表中。通常前缀是L,但目标可能使用其他字符串。.L是比较受欢迎的。

LLVM对inline
asm的支持与Clang的GCC兼容的inline-asm支持的要求紧密相关。因此,这里列出的特征集以及约束和修饰符代码与GCC内联asm支持中的代码类似或相同。然而,要清楚的是,这里描述的模板和约束字符串的语法与GCC和Clang所接受的语法并不相同,并且尽管大多数约束字母是通过Clang原样传递的,但当从C源代码转换为LLVM程序集时,有些字符会被转换为其他代码。

一个内联汇编表达式的例子是:

i32 (i32) asm "bswap $0", "=r,r"

内联汇编程序表达式只能用作调用(call)或调用(invoke)指令的被调用者操作数。因此,通常我们有:

%X = call i32 asm "bswap $0", "=r,r"(i32 %Y)

带有在约束列表中不可见的副作用的内联asms必须标记为具有副作用。这是通过使用’sideeffect’关键字完成的,如下所示:

call void asm sideeffect "eieio", ""()

在某些情况下,内联asms将包含无法工作的代码,除非堆栈以某种方式对齐,例如x86上的调用或SSE指令,但不会包含在asm中执行对齐的代码。编译器应该对asm可能包含的内容做出保守的假设,并且如果’alignstack’关键字存在,应该在序言中生成其通常的堆栈对齐代码:

call void asm alignstack "eieio", ""()

内联asms也支持使用非标准汇编方言。假定的方言是ATT。当’inteldialect‘关键字存在时,内联asm使用英特尔方言。目前,ATT和Intel是唯一支持的方言。一个例子是:

call void asm inteldialect "eieio", ""()

如果出现多个关键字,则’sideeffect‘关键字必须首先出现,’alignstack‘关键字第二关键字和’inteldialect‘关键字最后出现。

内联汇编约束字符串

约束列表是逗号分隔的字符串,每个元素包含一个或多个约束代码。

对于约束列表中的每个元素,将选择一个适当的寄存器或内存操作数,并且将对$0列表中的第一个约束,$1第二个等将使其可用于组件模板字符串扩展。

有三种不同类型的约束,它们通过约束代码前面的前缀符号进行区分:输出,输入和Clobber。必须始终按照以下顺序给出约束:先输出,然后输入,然后是clobbers。他们不能混在一起。

还有三种不同类型的约束代码:

  • 注册约束。这是一个寄存器类,或者是一个固定的物理寄存器。这种约束将分配一个寄存器,并且如果必要的话,将该参数或结果进行bitcast到适当的类型。
  • 内存约束。这种约束用于获取内存操作数的指令。不同的约束允许目标使用不同的寻址模式。
  • 立即值限制。这种约束是针对整数或其他立即值的,它可以直接渲染到指令中。各种特定于目标的约束条件允许为您希望使用的指令选择合适范围内的值。

输出约束

输出约束由“=”前缀(例如“=r”)指定。这表示程序集将写入此操作数,然后操作数将作为asm表达式的返回值提供。输出约束不会消耗调用指令中的参数。(除了下面关于间接输出的内容)。

通常,在读取所有输入之前,预计没有输出位置被汇编表达式写入。因此,LLVM可以将相同的寄存器分配给输出和输入。如果这不安全(例如,如果程序集包含两条指令,其中第一条写入一个输出,第二条读取输入并写入第二条输出),则必须使用“&”修饰符(例如“=&r”)来指定输出是“早期破坏”输出。将输出标记为“early-clobber”可确保LLVM不会对任何输入(除了与此输出关联的输入)使用相同的寄存器。

输入约束

输入约束没有前缀 –
只是约束代码。每个输入约束将从调用指令中消耗一个参数。asm不允许写入任何输入寄存器或存储单元(除非该输入连接到输出)。还要注意,如果LLVM可以确定它们必然都包含相同的值,则可以将多个输入全部分配给相同的寄存器。

通过提供一个整数作为约束字符串,输入约束可以将它们自己绑定到输出约束,而不是提供约束代码。被绑定的输入仍然会从调用指令中消耗一个参数,并且按照通常的方式在asm模板编号中占据一个位置

它们将被简单地限制为始终使用与其绑定的输出相同的寄存器。例如,一个约束字符串“=r,0”表示为输出分配一个寄存器,并将该寄存器用作输入(它是第0个约束)。

允许将输入连接到“早期破坏(early-clobber)”输出。在这种情况下,没有
其他输入可能与连接到早期触发器的输入共享相同的寄存器(即使其他输入具有相同的值)。

您只能将输入绑定到具有寄存器约束但不受内存约束的输出。只有一个输入可能与输出相关联。

还有一个“有趣”的特性,值得一点解释:如果寄存器类约束分配的寄存器对于作为输入提供的值类型操作数来说太小,则输入值将被分成多个寄存器,并且所有寄存器传递给内联asm。

但是,此功能通常不如您想象的那么有用。

首先,寄存器不保证连续。因此,在那些具有多条连续指令操作指令的体系结构上,这不是支持它们的适当方式。(例如,32位SparcV8具有64位加载,该指令只需要一个32位寄存器,然后硬件将加载到指定的寄存器和下一个寄存器中。内联asm的此功能对于此的支持将不会有用。)

几个目标提供了一个模板字符串修改,允许两寄存器操作数的第二个寄存器明确的访问(例如MIPS L,M和 D)。在这样的体系结构中,您实际上可以访问第二个已分配的寄存器(但是,仍然没有任何后续的寄存器)。但是,在这种情况下,为了清晰起见,将这个值简化为两个独立的操作数仍然可能更好。(例如,请参阅AX86 上的约束描述,尽管该特性仅用于此功能,但使用并不是一个好主意).

间接输入和输出

间接输出或输入约束可以由“*”修饰符(在输出的情况下在“=”之后)指定。这表明asm将写入或读取作为输入参数提供的地址的内容。(注意,在这种方式,间接输出更像一个输入而不是输出:只是像输入,它们消耗的调用表达式的参数,而不是产生一个返回值。间接输出约束是“输出”仅是希望在asm可以写入输入内存位置的内容,而不是从中读取)。

这通常用于内存约束,例如“=*m”,以将变量的地址作为值传递。

也可以使用间接寄存器约束,但仅限于输出(例如“=*r”)。这会导致LLVM正常地为输出值分配一个寄存器,然后在提供的内联asm之后,单独发送一个存储到作为输入提供的地址。(与在asm语句后明确写入store相比,此功能提供了什么值尚不清楚,而且它只能生成更糟糕的代码,因为它绕过了许多优化过程,我建议不要使用它。)

Clobber约束

一个clobber约束由一个“~”前缀表示。clobber不会消耗输入操作数,也不会生成输出。Clobbers不能使用任何一般的约束代码字母

它们可能只使用明确的寄存器约束,例如“~{eax}”。一个例外是,“~{memory}
的clobber字符串表示程序集写入任意未声明的内存位置 –
不仅是由声明的间接输出指向的内存。

请注意,输出约束中也存在的clobbering命名寄存器是不合法的。

约束代码

潜在的前缀来了约束代码或代码之后。

约束代码可以是单个字母(例如“r”),“^”字符后跟两个字母(例如“^wc”)或“{”寄存器名称“ }”(例如“{eax}”)。

通常选择单字母和双字母约束代码与GCC的约束代码相同。

一个约束可能包含一个或多个约束代码,而让LLVM选择使用哪一个约束代码。这主要包括与来自clang的GCC
inline asm的翻译兼容。

有两种方式可以指定替代方案,并且可以在内联asm约束列表中使用其中之一或两者。

  1. 相互追加代码,制作约束代码集。例如“im”或“ {eax}m”。这意味着“选择集合中的任何选项”。对约束列表中的每个约束独立进行约束的选择。
  2. 在约束代码集之间使用“|”,创建替代方案。约束列表中的每个约束都必须具有相同数量的备选集。使用这种语法,约束列表中所有项目中的相同备选项将一起选择。

把它们放在一起,你可能会有两个操作数约束字符串,像”rm|r,ri|rm“。这表明如果操作数0是r或m,则操作数1可以是r或i。如果操作数0是r,则操作数1可以是r或m。但是,操作数0和1不能都是m类型。

但是,不推荐使用其中任何一种替代功能,因为LLVM无法对使用哪种替代功能做出明智选择。(在当前需要选择的时候,没有足够的信息可以用聪明的方式来实现。)因此,它只是试图做出最有可能编译的选择,而不是最优性能的选择。(例如,给定“rm”,它总是选择使用内存,而不是寄存器)。而且,如果给定多个寄存器或多个寄存器类,它将简单地选择第一个。(实际上,目前它甚至不确保明确指定的物理寄存器是唯一的,因此指定多个物理寄存器作为替代,例如
{r11}{r12},{r11}{r12},将r11分配给两个操作数,而不是所有打算的。)

支持的约束代码列表

一般来说,约束代码的行为与GCC中的一样。LLVM的支持通常是在’按需’的基础上实现的,以支持GCC支持的C
inline代码。LLVM和GCC之间的行为不匹配可能表明LLVM存在错误。

所有目标通常都支持一些约束代码:

  • r:目标通用寄存器类中的寄存器。
  • m:存储器地址操作数。它支持哪些寻址模式,典型的例子是寄存器,寄存器+寄存器偏移量,或寄存器+直接偏移量(某些目标特定的大小)。
  • i:一个整数常量(目标特定宽度)。允许简单的即时或可重定位的值。
  • n:一个整数常量 – 不包括可重定位值。
  • s:一个整数常量,但只允许重定位值。
  • X:允许任何类型的操作数,不受任何限制。通常用于为asm分支或call传递标签。
  • {register-name}:需要完整的指定物理寄存器。

其他约束是针对具体目标的:

AArch64:

  • X:允许任何类型的操作数,不受任何限制。通常用于为asm分支或call传递标签。
  • z:一个立即数整数0.输出WZR或者XZR视情况而定。
  • I:对一个ADD或SUB指令有效的立即整数,即0到4095,可选的移位12。
  • J:一个立即数,取反时对一个ADD或
    SUB指令有效,即-1到-4095,可选左移12。
  • K:一个直接整数,它是有效的“位掩码即时32”的逻辑指令等AND,EOR或ORR与32位寄存器。
  • L:一个直接整数,它是有效的“位掩码即时64”的逻辑指令等AND,EOR或ORR与64位寄存器。
  • M:与MOV32位寄存器上的程序集别名一起使用的立即整数。这是一个超集K:除了bitmask立即数,还允许立即可以装载单个MOVZ或MOVL指令的整数
  • N:用于MOV64位寄存器上的程序集别名的立即整数。这是一个超集L。
  • Q:存储器地址操作数必须位于单个寄存器中(无偏移量)。(但是,LLVM目前也为m约束做了这个。)
  • r:32位或64位整数寄存器(W *或X *)。
  • w:一个32,64或128位浮点/ SIMD寄存器。
  • x:较低的128位浮点/ SIMD寄存器(V0至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寄存器:s0-s31,d0-d16,或 q0-q8。

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寄存器:s0-s31,d0-d16,或 q0-q8。

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:适用于间接跳转(始终25)的32位或64位GPR寄存器 。
  • 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:对于或类型,当启用QPX时,为128或256位QPX寄存器(),否则为128位altivec向量寄存器()。4
    x f324 x f64Q0-Q31V0-V31
  • 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:立即整数0x7fffffff。
  • Q:具有基地址和12位立即无符号位移的存储器地址操作数。
  • R:一个带有基地址的内存地址操作数,一个12位立即无符号位移和一个索引寄存器。
  • S:一个内存地址操作数,带有一个基址和一个20位立即带符号的位移。
  • T:一个带有基地址的内存地址操作数,一个20位立即带符号位移和一个索引寄存器。
  • r或者d:一个32位,64位或128位整数寄存器。
  • a:32,64或128位整数地址寄存器(不包括地址上下文中评估为0的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:如果启用MMX,则为64位MMX寄存器。
  • x:如果启用SSE:SSE寄存器中的32位或64位标量操作数或128位向量操作数。如果AVX也被使能,也可以是AVX寄存器中的256位向量操作数。如果AVX-512也被使能,也可以是AVX512寄存器中的512位向量操作数,否则会出错。
  • Y:同x,如果SSE2被启用,否则会出现错误。
  • A:特殊情况:首先为EAX分配EAX,然后再为EDX分配单个操作数(在32位模式下,64位整数操作数将分成两个寄存器)。不建议使用此约束,因为在64位模式下,64位操作数只会分配给RAX
    – 如果需要两个32位操作数,则最好在将其分配给它之前自行分割asm声明。

XCore:

  • r:一个32位整数寄存器。

Asm模板参数修饰符

在asm模板字符串中,可以在操作数引用上使用修饰符,如“${0:n}”。

一般来说,修饰符的行为与GCC中的相同。LLVM的支持通常是在’按需’的基础上实现的,以支持GCC支持的C
inline代码。LLVM和GCC之间的行为不匹配可能表明LLVM存在错误。

目标无关的:

  • c:不带目标特定的直接标点符号(例如无$前缀)打印一个立即的整数常量。
  • n:取消并打印立即数整数常量,不带目标特定的直接标点符号(例如无$前缀)。
  • l:打印为无标签的标签,没有特定于目标的标签标点(例如无$前缀)。

AArch64:

  • w:用w名称而不是x名称打印GPR寄存器。例如,而不是x30打印w30。
  • x:用x*名称打印GPR寄存器。(无论如何,这是默认的)。
  • b,h,s,d,q:打印浮点/ SIMD寄存器有
    b,h,s,d,或q名称,而不是默认的 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四路寄存器的低双字寄存器。
  • f:打印NEON四路寄存器的高位双字寄存器。
  • m:打印没有[和] 装饰的内存操作数的基址寄存器。

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-form指令的格式化程序。(目前始终打印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:打印内存引用或操作数以用作调用指令的参数。(例如(rip),即使它是PC相对的,也省略。)

XCore:

  • 没有额外的修饰符。

内联Asm元数据

包装内联asm节点的调用指令可能会附加一个“!srcloc”MDNode,它包含一个常量整数列表。如果存在,则当通过LLVMContext
错误报告机制报告错误时,代码生成器将使用该整数作为位置cookie值。这允许前端将内联asm中发生的后端错误与产生它的源代码关联起来。例如:

call void asm sideeffect "something bad", ""(), !srcloc !42
...
!42 = !{ i32 1234567 }

直到前端才能理解它在IR中的神奇数字。如果MDNode包含多个常量,则代码生成器将使用与发生错误的asm行相对应的那个常量。