GCC内联汇编,混合寄存器大小(x86)

13

有人知道我该如何消除以下汇编器警告吗?

代码是x86,32位:

int test (int x)
{
  int y;
  // do a bit-rotate by 8 on the lower word. leave upper word intact.
  asm ("rorw $8, %0\n\t": "=q"(y) :"0"(x));
  return y;
}

如果我编译它,会得到以下(非常有效的)警告:
Warning: using `%ax' instead of `%eax' due to `w' suffix

我需要的是一种告诉编译器/汇编器我想要访问%0的低16位子寄存器的方法。访问字节子寄存器(在这种情况下为AL和AH)也很好知道。我已经选择了“q”修饰符,因此编译器被迫使用EAX、EBX、ECX或EDX。我已确保编译器必须选择具有子寄存器的寄存器。
我知道我可以强制asm代码使用特定的寄存器(及其子寄存器),但我想将寄存器分配工作留给编译器。
4个回答

22

如果我没记错的话,您可以使用%w0。我也刚刚测试过了。:-)

int
test(int x)
{
    int y;
    asm ("rorw $8, %w0" : "=q" (y) : "0" (x));
    return y;
}

编辑:针对原帖,是的,你也可以这样做:
int
test(int x)
{
    int y;
    asm ("xchg %b0, %h0" : "=Q" (y) : "0" (x));
    return y;
}

对于x86,它在手册的扩展Asm部分的x86操作数修饰符部分中有记录。
对于非x86指令集,您可能需要查看GCC源代码中的.md文件。例如,在正式记录之前,gcc/config/i386/i386.md是唯一可以找到此信息的地方。
(相关:在GNU C内联asm中,xmm/ymm/zmm的大小覆盖修饰符适用于单个操作数吗? 适用于向量寄存器。)

我也测试了。你知道低字节和高字节的修饰符吗? - Nils Pipenbrinck
1
xchg %al, %ah 在英特尔CPU上需要3个微操作,读取16位ax会导致部分寄存器停顿或某些CPU上需要额外的微操作。 ror $8, %ax 只需要1个微操作,因此它绝对是更可取的。此外,操作数修饰符现在已经在手册中有所说明(使用相同的示例,这可能不是巧合:P)。另请参阅:矢量寄存器的操作数修饰符:https://dev59.com/plsW5IYBdhLWcg3w66qH - Peter Cordes

9

很久以前,但我可能需要这个作为自己未来的参考...

补充Chris的精彩答案,关键是在输出操作数的数字和百分号之间使用修饰符。例如,"MOV %1, %0" 可能会变成 "MOV %q1, %w0"

我在constraints.md中找不到任何内容,但/gcc/config/i386/i386.cprint_reg()源代码中有这个有用的注释:

/* Print the name of register X to FILE based on its machine mode and number.
   If CODE is 'w', pretend the mode is HImode.
   If CODE is 'b', pretend the mode is QImode.
   If CODE is 'k', pretend the mode is SImode.
   If CODE is 'q', pretend the mode is DImode.
   If CODE is 'x', pretend the mode is V4SFmode.
   If CODE is 't', pretend the mode is V8SFmode.
   If CODE is 'h', pretend the reg is the 'high' byte register.
   If CODE is 'y', print "st(0)" instead of "st", if the reg is stack op.
   If CODE is 'd', duplicate the operand for AVX instruction.
 */

ix86_print_operand()下方的评论提供了一个例子:

b -- 打印指定操作数所表示寄存器的QImode名称。

如果operands[0]是reg 0,则%b0将打印%al。

GCC Internals文档的输出模板部分列出了几个更有用的选项:

“%cdigit”可用于替换常量值的操作数,而无需通常表示立即操作数的语法。 “%ndigit”与“%cdigit”类似,只是在打印之前对常量的值进行了取反。 “%adigit”可用于将操作数替换为内存引用,实际操作数被视为地址。当输出“加载地址”指令时,这可能很有用,因为通常汇编器语法要求您将操作数写成内存引用的形式。 “%ldigit”用于将label_ref替换为跳转指令。 “%=”输出一个数字,该数字在整个编译中每个指令都是唯一的。这对于使本地标签在生成多个汇编器指令的单个模板中被多次引用非常有用。
“%c2”结构允许使用偏移量正确格式化LEA指令:
#define ASM_LEA_ADD_BYTES(ptr, bytes)                            \
    __asm volatile("lea %c1(%0), %0" :                           \
                   /* reads/writes %0 */  "+r" (ptr) :           \
                   /* reads */ "i" (bytes));

请注意 '%c1' 中关键但文档稀少的 'c'。这个宏与...等价。
ptr = (char *)ptr + bytes

但是不使用通常的整数算术执行端口。
编辑以添加:
在x64中进行直接调用可能很困难,因为它需要另一个未记录的修饰符:“%P0”(似乎是用于PIC)。
#define ASM_CALL_FUNC(func)                                         \
    __asm volatile("call %P0") :                                    \
              /* no writes */ :                                     \
              /* reads %0 */ "i" (func))                           

在GCC中,小写的“p”修饰符似乎也具有相同的功能,尽管ICC仅识别大写的“P”。更多细节可能可在/gcc/config/i386/i386.c找到。搜索“'p'”。

2
“full”表现在源文件中,作为函数ix86_print_operand()之前的注释。它还提到了(其中之一)%p../%P.. - FrankH.
未来的读者请注意:QI表示四分之一整数,HI表示半整数,SI表示单精度整数,DI表示双精度整数,TI表示四倍精度整数宽度。 - Peter Cordes
仅供历史兴趣,这些现在在GCC手册中有文档记录,并使用此问题中的示例,可能是由于OP报告http://gcc.gnu.org/bugzilla/show_bug.cgi?id=37621 - 最初,GCC开发人员不想通过记录它们来将这些内部固定下来,但在某个时候完成了。 - Peter Cordes

3

在我思考这个问题时……你应该用大写的“Q”约束代替Chris第二种解决方案中的“q”约束:

int
test(int x)
{
    int y;
    asm ("xchg %b0, %h0" : "=Q" (y) : "0" (x));
    return y;
}

在64位模式下,“q”和“Q”略有不同,您可以获取所有整数寄存器(ax、bx、cx、dx、si、di、sp、bp、r8-r15)的最低字节。但是,对于四个原始的386寄存器(ax、bx、cx、dx),您只能获取第二低字节(例如ah)。


0

显然有一些技巧可以做到这一点...但效率可能不是很高。32位x86处理器通常在操纵通用寄存器中的16位数据方面较慢。如果性能很重要,您应该对其进行基准测试。

除非这是(a)性能关键和(b)证明更快,否则我会为自己节省一些维护麻烦并只使用C语言来完成它:

uint32_t y, hi=(x&~0xffff), lo=(x&0xffff);
y = hi + (((lo >> 8) + (lo << 8))&0xffff);

使用 GCC 4.2 和 -O2,这可以被优化为六条指令...


2
6条指令怎么可能比1条指令更快?!我的计时测试(进行了10亿次运行,5次试验)结果如下:我的版本=(4.38、4.48、5.03、4.10、4.18),你的版本=(5.33、6.21、5.62、5.32、5.29)。 - C. K. Young
所以,我们正在看一个20%的速度提升。这不是“快多了”吗? - C. K. Young
@丹,我需要那个低位字节交换原语来进行更大的微调。我知道在32位代码中进行16位操作有点慢而且不受欢迎,但该代码将被其他32位操作包围。我希望16位代码的速度会在乱序调度中被忽略。最终我想要实现一个机制,可以就地执行一个dword的所有24种可能的字节置换。为此,您最多只需要三个指令:低字节交换(例如xchg al,ah),bswap和32位旋转。这种就地方式不需要任何常量(更快的代码获取/解码时间)。 - Nils Pipenbrinck
1
Chris,你说得很对... 你的版本似乎确实更快。但是,并不像6条指令比1条指令快那么多,这就是我所警告的。实际上我自己没有做过比较,所以感谢你测试它! - Dan Lenski
1
在Sandybridge系列的CPU上,与2008年的Core2或Nehalem CPU相比,差异会更大,后者在插入合并uop时会停顿2或3个周期,而SnB则不会停顿。在Haswell上,部分寄存器减速完全被消除。有关部分寄存器惩罚的信息,请参见Agner Fog的微体系结构pdf。http://stackoverflow.com/tags/x86/info - Peter Cordes

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接