gcc内联汇编 - 操作数类型不匹配"add",试图创建无分支代码

use*_*875 5 c performance x86 assembly inline-assembly

我正在尝试做一些代码优化来消除分支,原始的c代码是

if( a < b ) 
   k = (k<<1) + 1;
else
   k = (k<<1)
Run Code Online (Sandbox Code Playgroud)

我打算用下面的汇编代码替换它

mov a, %rax 
mov b, %rbx
mov k, %rcx
xor %rdx %rdx
shl 1, %rcx
cmp %rax, %rax
setb %rdx
add %rdx,%rcx
mov %rcx, k 
Run Code Online (Sandbox Code Playgroud)

所以我写了内联汇编代码,如打击,

#define next(a, b, k)\
 __asm__("shl $0x1, %0; \
         xor %%rbx, %%rbx; \
         cmp %1, %2; \
         setb %%rbx; \
         addl  %%rbx,%0;":"+c"(k) :"g"(a),"g"(b))
Run Code Online (Sandbox Code Playgroud)

当我编译下面的代码时,我得到了错误:

operand type mismatch for `add'
operand type mismatch for `setb'
Run Code Online (Sandbox Code Playgroud)

我该如何解决?

sco*_*ttt 10

以下是代码中的错误:

  1. 错误:'cmp'的操作数类型不匹配 - CMP的一个操作数必须是寄存器.你可能正在生成试图比较两个immediates的代码.将第二个操作数的约束从"g"更改为"r".(参见GCC手册 - 扩展的Asm - 简单约束)
  2. 错误:'setb'的操作数类型不匹配 - SETB只需要8位操作数,即 setb %bl工作时setb %rbx不工作.
  3. C表达式T = (A < B)应转换为cmp B,A; setb TAT&T x86汇编语法.你有两个错误的CMP操作数.请记住,CMP就像SUB一样.

一旦你意识到汇编程序产生了前两个错误消息,那么调试它们的技巧就是查看gcc生成的汇编程序代码.尝试使用x86操作码参考gcc $CFLAGS -S t.c比较有问题的行.专注于每条指令的允许操作数代码,您将很快看到问题.t.s

在下面发布的固定源代码中,我假设您的操作数是无符号的,因为您使用的是SETB而不是SETL.我使用RBX切换到RCX来保存临时值,因为RCX是ABI中的一个调用阻塞寄存器,并使用"=&c"约束将其标记为早期操作数,因为RCX在输入之前被清除 a并被b读取:

#include <stdio.h>
#include <stdint.h>
#include <inttypes.h>

static uint64_t next(uint64_t a, uint64_t b, uint64_t k)
{
    uint64_t tmp;
    __asm__("shl $0x1, %[k];"
        "xor %%rcx, %%rcx;"
        "cmp %[b], %[a];"
        "setb %%cl;"
        "addq %%rcx, %[k];"
        : /* outputs */ [k] "+g" (k), [tmp] "=&c" (tmp)
        : /* inputs  */ [a] "r" (a), [b] "g" (b)
        : /* clobbers */ "cc");
    return k;
}

int main()
{
    uint64_t t, t0, k;
    k = next(1, 2, 0);
    printf("%" PRId64 "\n", k);

    scanf("%" SCNd64 "%" SCNd64, &t, &t0);
    k = next(t, t0, k);
    printf("%" PRId64 "\n", k);

    return 0;
}
Run Code Online (Sandbox Code Playgroud)

main()转换为:

<+0>:   push   %rbx
<+1>:   xor    %ebx,%ebx
<+3>:   mov    $0x4006c0,%edi
<+8>:   mov    $0x1,%bl
<+10>:  xor    %eax,%eax
<+12>:  sub    $0x10,%rsp
<+16>:  shl    %rax
<+19>:  xor    %rcx,%rcx
<+22>:  cmp    $0x2,%rbx
<+26>:  setb   %cl
<+29>:  add    %rcx,%rax
<+32>:  mov    %rax,%rbx
<+35>:  mov    %rax,%rsi
<+38>:  xor    %eax,%eax
<+40>:  callq  0x400470 <printf@plt>
<+45>:  lea    0x8(%rsp),%rdx
<+50>:  mov    %rsp,%rsi
<+53>:  mov    $0x4006c5,%edi
<+58>:  xor    %eax,%eax
<+60>:  callq  0x4004a0 <__isoc99_scanf@plt>
<+65>:  mov    (%rsp),%rax
<+69>:  mov    %rbx,%rsi
<+72>:  mov    $0x4006c0,%edi
<+77>:  shl    %rsi
<+80>:  xor    %rcx,%rcx
<+83>:  cmp    0x8(%rsp),%rax
<+88>:  setb   %cl
<+91>:  add    %rcx,%rsi
<+94>:  xor    %eax,%eax
<+96>:  callq  0x400470 <printf@plt>
<+101>: add    $0x10,%rsp
<+105>: xor    %eax,%eax
<+107>: pop    %rbx
<+108>: retq   
Run Code Online (Sandbox Code Playgroud)

您可以在每次调用之前看到next()移入RSI的结果printf().


Mat*_*son 9

鉴于gcc(它看起来像gcc内联汇编程序)产生:

leal    (%rdx,%rdx), %eax
xorl    %edx, %edx
cmpl    %esi, %edi
setl    %dl
addl    %edx, %eax
ret
Run Code Online (Sandbox Code Playgroud)

int f(int a, int b, int k)
{
  if( a < b ) 
    k = (k<<1) + 1;
  else
    k = (k<<1);

  return k;
}
Run Code Online (Sandbox Code Playgroud)

它会认为编写自己的内联汇编程序完全是浪费时间和精力.

与往常一样,在开始编写内联汇编程序之前,请检查编译器实际执行的操作.如果您的编译器没有生成此代码,那么您可能需要将编译器的版本升级到更新的版本(我向Jan Hubicka报告了这种情况[当时为g86维护者x86-64] ca 2001,并且我确定它已经在gcc中使用了很长时间).


iab*_*der 8

您可以这样做,编译器不会生成分支:

k = (k<<1) + (a < b) ;
Run Code Online (Sandbox Code Playgroud)

但如果你必须,我在你的代码中修改了一些东西,现在它应该按预期工作:

__asm__(
        "shl  $0x1, %0; \
        xor  %%eax, %%eax; \
        cmpl %3, %2; \
        setb %%al; \
        addl %%eax, %0;"
        :"=r"(k)        /* output */
        :"0"(k), "r"(a),"r"(b)  /* input */
        :"eax", "cc"   /* clobbered register */ 
);
Run Code Online (Sandbox Code Playgroud)

需要注意的是setb需要一个reg8mem8,你应该添加eax到修饰列表,因为你改变它,也cc只是为了安全起见,作为登记的限制,我不知道为什么你使用的那些,但=rr工作就好了.您需要添加k输入和输出列表.GCC-Inline-Assembly-HOWTO还有更多内容