12

有谁知道我怎样才能摆脱以下汇编程序警告?

代码是 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

5 回答 5

20

%w0如果我没记错你可以使用。我也刚测试过。:-)

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

编辑:响应 OP,是的,您也可以执行以下操作:

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

对于 x86,它记录在手册扩展 Asm 部分的x86 操作数修饰符部分

对于非 x86 指令集,您可能必须.md在 GCC 源代码中挖掘它们的文件。例如,gcc/config/i386/i386.md在正式记录之前,它是唯一可以找到它的地方。

(相关:在 GNU C 内联汇编中,对于单个操作数,xmm/ymm/zmm 的大小覆盖修饰符是什么?对于向量寄存器。)

于 2008-09-23T02:01:58.397 回答
9

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

加上克里斯的好回答说,关键是在“%”和输出操作数的数量之间使用修饰符。例如,"MOV %1, %0"可能变成"MOV %q1, %w0".

我在 constraints.md 中找不到任何内容,但是/gcc/config/i386/i386.c在源代码中有这个潜在有用的注释print_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 名称。

如果操作数 [0] 为 reg 0,则 %b0 将打印 %al。

GCC 内部文档的输出模板下列出了一些更有用的选项:

'%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));

请注意“ ”中关键但记录很少的“c” %c1。这个宏相当于

ptr = (char *)ptr + bytes

但不使用通常的整数算术执行端口。

编辑添加:

在 x64 中进行直接调用可能很困难,因为它需要另一个未记录的修饰符:' %P0'(这似乎适用于 PIC)

#define ASM_CALL_FUNC(func)                                         \
    __asm volatile("call %P0") :                                    \
              /* no writes */ :                                     \
              /* reads %0 */ "i" (func))                           

小写的“p”修饰符在 GCC 中的作用似乎也相同,尽管 ICC 只识别大写的“P”。/gcc/config/i386/i386.c可能提供更多详细信息。搜索“'p'”。

于 2013-07-24T04:25:31.473 回答
3

当我在考虑它时......你应该在克里斯的第二个解决方案中用大写的“Q”约束替换“q”约束:

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

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

于 2008-09-23T17:12:58.753 回答
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 可以优化到六个指令......

于 2008-09-23T06:20:31.740 回答
0

明白了。好吧,如果这是一个您将一遍又一遍地重用的原始例程,我对此没有任何争论……克里斯指出的寄存器命名技巧是一个很好的技巧,我必须记住。

如果它也能成为标准的 GCC 文档,那就太好了!

于 2008-09-23T16:41:26.643 回答