高级CGNU C/C++ 内联汇编——进阶——约束详解
Posted 从善若水
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了高级CGNU C/C++ 内联汇编——进阶——约束详解相关的知识,希望对你有一定的参考价值。
本人就职于国际知名终端厂商,负责modem芯片研发。
在5G早期负责终端数据业务层、核心网相关的开发工作,目前牵头6G算力网络技术标准研究。
博客内容主要围绕:
5G协议讲解
算力网络讲解(云计算,边缘计算,端计算)
高级C语言讲解
Rust语言讲解
文章目录
GNU C/C++ 内联汇编——进阶——约束详解
ASM 操作码的 constraints
Simple Constraints ——Basic use of constraints
最简单的约束是一个由字母组成的字符串,每个字母描述一种允许的操作数,以下是允许的字母:
Constraints | 含义 |
---|---|
空格 | 空白字符将被忽略,可以插入除第一个位置以外的任何位置。这使得不同操作数的每个选项在机器描述中对齐,即使它们有不同数量的约束和修饰符。 |
‘m’ | 允许内存操作数,它可以是计算机支持的任何类型的地址。注意,用于通用内存约束的字母可以由后端使用TARGET_MEM_CONSTRAINT宏重新定义。 |
‘o’ | 允许使用内存运算对象,但仅当该地址是可偏移的(offsettable)。这意味着可以向地址添加一个小整数(实际上是操作数的字节宽度,由其机器模式决定),结果也是一个有效的内存地址。 例如,一个常量地址是可偏移的;一个寄存器和一个常量之和的地址(只要一个稍大的常量也在机器支持的地址偏移量范围内)也是一样的;但自动递增或自动递减地址是不可偏移的。更复杂的间接/索引地址可以偏移,也可以不偏移,这取决于机器支持的其它寻址模式。 请注意,在一个可以被另一个操作数匹配的输出操作数中,约束字母’ o ‘只有在同时伴有’ < ‘(如果目标机器有前任寻址)和’ > '(如果目标机器有前增量寻址)时才有效。 |
‘V’ | 一种不可偏移的内存运算对象。换句话说,任何符合m约束但不符合o约束的东西。 |
‘<’ | 允许具有自减法寻址的内存操作数(无论是前置或后置减法)。在内联asm中,只有当操作数在可以处理副作用的指令中恰好使用一次时,才允许此约束。在inline asm模式的约束字符串中不使用带有’ < '的操作数或在多个指令中使用它是无效的,因为副作用不会被执行或将被执行不止一次。 |
‘>’ | 允许使用具有自动递增寻址(前置递增或后置递增)的内存运算对象。在inline asm中,应用与’ < '相同的限制。 |
‘r’ | 允许在通用寄存器中使用寄存器运算对象。 |
‘i’ | 允许使用立即整型操作数(具有常值的操作数)。这包括其值只有在汇编时或以后才知道的符号常量。 |
‘n’ | 允许具有已知数值的立即整数操作数。许多系统不能为小于一个字宽的操作数支持汇编时常量。这些操作数的约束应该使用’ n ‘而不是’ i '。 |
‘I’, ‘J’, ‘K’, … ‘P’ | 在’ I ‘到’ P ‘范围内的其他字母可以以一种依赖于机器的方式定义,以允许在指定范围内具有明确整数值的整数操作。例如,在68000上,’ I '被定义为表示值1到8的范围。这是移位指令中允许作为移位计数的范围。 |
‘E’ | 允许立即浮点操作数 (表达式代码const_double),但前提是目标浮点数格式与运行编译器的主机的格式相同。 |
‘F’ | 允许立即浮点操作数 (表达式代码const_double或const_vector)。 |
‘G’, ‘H’ | ’ G ‘和’ H '可以以一种依赖于机器的方式定义,以允许特定值范围内的直接浮点运算对象。 |
‘s’ | 允许一个值不明确的立即整数操作数。 这可能看起来很奇怪,如果 insn(指令) 允许常量操作数的值在编译时未知,那么它肯定允许任何已知值。那么为什么要用 ‘s’ 而不是 ‘I’ 呢?因为,有时它可以生成更好的代码。 例如,在68000的全字指令中,可以使用一个立即操作数;但是,如果当前值介于-128和127之间,那么将该值加载到寄存器并使用寄存器可以得到更好的代码。这是因为载入寄存器可以通过 ’ moveq ’ 指令完成。我们将字母 ‘K’ 定义为 “-128到127范围外的任何整数” ,然后在操作数约束中指定 ‘Ks’ 。 |
‘g’ | 任何通用寄存器、内存、立即整数 的操作数都被允许。 |
‘X’ | 任何操作数都是允许的。 |
‘0’, ‘1’, ‘2’, … ‘9’ | 允许匹配指定操作数的操作数。如果一个数字和同一个选项中的字母一起使用,这个数字应该放在最后。 这个数字个数允许大于一个数字。如果连续遇到多个数字,则将它们解释为一个十进制整数。不太可能产生歧义,因为到目前为止,将’ 10 '解释为匹配操作数1或操作数0都是不可取的。如果需要这样做,可以使用多个替代方案。 这被称为匹配约束(matching constrain),它的真正含义是,汇编程序只有一个操作数来填充asm区分的两个角色(在输出中分配与相应输入中相同的寄存器)。例如,加法指令使用两个输入操作数和一个输出操作数,但在大多数CISC机器上,加法指令实际上只有两个操作数,其中一个是输入-输出操作数: addl #35,r12 |
‘p’ | 允许一个有效的内存地址的操作数。用于“load address” and “push address”指令。 约束中的’ p '必须和 address_operand一起使用,作为match_operand中的谓词。这个谓词将match_operand中指定的模式转换为有效地址下的内存引用(memory reference)模式。 |
other-letters | 其他字母可以以机器相关的方式定义,以代表特定的寄存器类或其他任意操作数类型。’ d ', ’ a ‘和’ f '是在68000/68020上定义的,代表数据、地址和浮点寄存器。 |
Multiple Alternative Constraints
有时一条指令有多个可选的操作数集。例如,在68000上,一条 logical-or 指令可以将寄存器或立即数存入到内存中,也可以将任何类型的操作数存入寄存器中,但它不能将一个值从memory location 存入到另一个memory location。
这些约束被表示为多个选项。可选方案可以用每个操作数的一系列字母来描述。操作数的总体约束是由第一个选项中的这个操作数的字母构成的,然后逗号开始第二个可选项的描述,以此类推,直到最后一个选项。一条指令的所有操作数必须有相同数量的可选操作。
所以,68000的 logical-or 第一种方式可以写成 “+m” (output) : “ir” (input)。第二个可以是 “+r” (output): “irm” (input)。因为两个内存位置不能在一条指令中使用,所以不能写成 “+rm” (output) : “irm” (input)。使用多个选项,可以写成 “+m,r” (output) : “ir,irm” (input)。给编译器提供所有可选的方案,有助于编译器根据当前环境选择最高效的实现方案。
在模板中没有办法确定选择了哪个选项。然而,你可以用 __builtin_constant_p 这样的内置函数来包装你的asm语句,以达到预期的结果。
Constraint Modifier Characters——更精确地控制 Constraints 的影响
以下是约束修饰符字符:
Modifier | 含义 |
---|---|
‘=’ | 这个操作数被这条指令写入,先前的值被丢弃并被新数据替换。在有多个选项的约束中,必须在第一个选项中添加“=”约束修饰符,之后的其它选项会有同样的效果,且不允许再添加“=”约束修饰符。 |
‘+’ | 这个操作数同时被指令读写。 当编译器修复操作数以满足约束时,它需要知道哪些操作数被指令读取,哪些操作数被指令写入。’ = ‘标识一个只被写入的操作数;’ + '标识可读可写的操作数。所有其它操作数都假定只被读取。 如果在约束中指定 ’ = ’ 或 ’ + ’ ,则将其放在约束字符串的第一个字符中。在有多个选项的约束中,必须在第一个选项中添加“+”约束修饰符,之后的其它选项会有同样的效果,且不允许再添加“+”约束修饰符。 |
‘&’ | 这个操作数是一个 early-clobber 操作数,它在指令使用输入操作数之前被写入了值。因此,这个操作数不能位于被指令读取的寄存器中,也不能位于作为任何内存地址组成部分的寄存器中。 在有多个选项的约束中,有时一个选项需要’ & ‘,而其他选项则不需要。例如,看68000的“movdf” insn。有多个选项的约束‘&’仅仅对包含有‘&’的那个约束起作用,其它没有‘&’的约束不受影响。 如果一个被指令读取的操作数在写入早期结果之前仅作为一个输入,则可以将其绑定到early-clobber操作数。当只有部分读操作数会受到early-clobber的影响时,这种方法通常可以让GCC生成更好的代码。例如,ARM的’ mulsi3 ’ 指令。 此外,如果early-clobber操作数也是一个读/写操作数,则该操作数只有在被使用后才被写入。 ’ & ‘通常与’ = ‘或’ + '一起使用。由于early-clobber操作数总是被写入,只读的early-clobber操作数是病态的,将被编译器拒绝。 |
‘%’ | 声明这个操作数可以和下一个操作数互换。这意味着在生成指令时,这个操作数和下一个操作数的顺序可以互换。此修饰符只能用于输入操作数,而且不能在最后一个操作数上。’ % ’ 会应用到所有的可选约束中,并且必须以第一个字符的形式出现在约束中。 例如,asm(" fadd %0, %1, %2" : “=f” © : “%f” (a), “f” (b) ); 这个asm计算操作数a和b的和,并将结果写入操作数c。%修饰符表示,如果编译器可以通过交换操作数a和b生成更好的代码,那么可以交换操作数a和b。 GCC只能处理asm中的一个交换对。如果使用多个 ‘%’,编译器可能会报告错误。注意,如果两个约束选项完全相同,则不需要使用修饰符,这只会在重载过程中浪费时间。 |
Special constraints for some particular machines
AArch64 family
Special constraints | 含义 |
---|---|
k | 堆栈指针寄存器(SP) |
w | 浮点寄存器,高级SIMD向量寄存器或SVE向量寄存器 |
x | 像w,但限制寄存器0到15(含15)。 |
y | 像w,但限制寄存器0到7(包括7)。 |
Upl | 低8个SVE谓词寄存器之一(P0到P7) |
Upa | 任何一个SVE谓词寄存器(P0到P15) |
I | 整型常量,在ADD指令中作为立即操作数有效 |
J | 在 SUB 指令中作为立即操作数有效的整型常量 |
K | 可与32位逻辑指令一起使用的整型常量 |
L | 可以与64位逻辑指令一起使用的整型常量 |
M | 整型常量,在32位MOV伪指令中作为立即操作数有效。根据值的不同,MOV可以被汇编成几个不同的机器指令 |
N | 整型常量,在64位MOV伪指令中作为立即操作数有效 |
S | 绝对符号地址或标签引用 |
Y | 浮点常数零 |
Z | 整数常数为零 |
Ush | pc相对地址的高部分(第12位及以上)(4GB的内存) |
Q | 一种使用单一基寄存器而没有偏移量的内存地址 |
Ump | 一种内存地址,适用于SI、DI、SF和DF模式下的 load/store 指令对。 |
x86 family
Special constraints | 含义 |
---|---|
R | 遗留寄存器-在所有i386处理器上可用的8个整数寄存器(a, b, c, d, si, di, bp, sp)。 |
q | 任何可以访问rl(register low)的寄存器。32位模式下,a、b、c、d;在64位模式下,任何整数寄存器。 |
Q | 任何可访问rh(register high)的寄存器; a, b, c和d寄存器。 |
a | 寄存器 a |
b | 寄存器 b |
c | 寄存器 c |
d | 寄存器 d |
S | si 寄存器 |
D | di 寄存器 |
A | a和d寄存器。用于将双字(double word)结果返回在 ax:dx 寄存器的指令。返回单个字(single word)的值将以ax或dx的形式分配。例如在i386上实现rdtsc: unsigned long long rdtsc (void) unsigned long long tick; __asm__ __volatile__(“rdtsc”:"=A"(tick)); return tick; 这在x86-64上是不正确的,因为它将在ax或dx中分配tick。你必须使用下面的变量代替: unsigned long long rdtsc (void) unsigned int tickl, tickh; __asm__ __volatile__(“rdtsc”:"=a"(tickl),"=d"(tickh)); return ((unsigned long long)tickh << 32)|tickl; |
U | call-clobbered 整数寄存器 |
f | 任何80387浮点(堆栈)寄存器 |
t | 80387浮点堆栈(%st(0))的栈顶 |
u | 从80387浮点堆栈(%st(1))顶部开始的第二位 |
y | 任何MMX寄存器 |
x | 任何SSE寄存器 |
v | 任何EVEX可编码的SSE寄存器(%xmm0-%xmm31) |
Yz | 第一个SSE寄存器(%xmm0) |
I | 范围在0…31的整数常量,用于一个32位的移位操作 |
J | 范围在0…63的整数常量,用于一个64位的移位操作 |
K | 带符号的8位整数常量 |
L | 0xFF或0xFFFF |
M | 0, 1, 2,或3 (lea指令的偏移) |
N | 无符号8位整数常量(用于 in 和 out 指令) |
G | 标准80387浮点常量 |
C | SSE常数零操作数 |
e | 32位有符号整型常量,或已知适合该范围的符号引用(用于符号扩展的x86-64指令中的立即操作数)。 |
We | 32位有符号整型常量,或一个已知适合该范围的符号引用(用于需要 non-VOIDmode 立即操作数的符号扩展转换操作) |
Wz | 32位无符号整型常量,或一个已知适合该范围的符号引用(用于需要 non-VOIDmode 立即操作数的符号扩展转换操作) |
Wd | 128位整数常量,其中高64位和低64位字都满足e约束 |
Z | 32位无符号整型常量,或一个已知适合该范围的符号引用(用于零扩展的x86-64 立即操作数) |
Tv | VSIB地址操作数 |
Ts | 没有段寄存器的地址操作数 |
这里是从善若水的博客,感谢您的阅读⌨🖥🖱
文章链接
《GNU C/C++ 内联汇编编程指南全集》
《GNU C/C++ 内联汇编——入门级》
《GNU C/C++ 内联汇编——进阶——语法详解》
《GNU C/C++ 内联汇编——进阶——约束详解》
《GNU C/C++ 内联汇编——补充介绍》
《GNU C/C++ 内联汇编——实例参考》
《GNU C/C++ 内联汇编——Intel与ATT汇编语法对比》
以上是关于高级CGNU C/C++ 内联汇编——进阶——约束详解的主要内容,如果未能解决你的问题,请参考以下文章