x86 - 使用内联汇编设置位

3

我正在编写一个函数,使用内联汇编的 bts 指令来设置数字 x 的第 n 位。以下是我的函数:

uint32_t set_bit_assembly(uint32_t x, uint32_t n)
{
    asm( "movl %1, %%eax; bts %0, %%eax;"
         :"=&r"(x)
         :"r"(n)
        );
    return x;
}

我希望变量'n'和'x'分别成为movlbts的第一操作数。但是当我编译时,它将'x'用于movl并完全忽略了'n'。(我尝试交换%0和%1,但没有帮助)。您能告诉我我哪里出错了吗?以下是生成的汇编代码:

00000043 <set_bit_assembly>:
  43:   55                      push   %ebp
  44:   89 e5                   mov    %esp,%ebp
  46:   83 ec 10                sub    $0x10,%esp
  49:   8b 55 0c                mov    0xc(%ebp),%edx
  4c:   89 d0                   mov    %edx,%eax
  4e:   0f ab c0                bts    %eax,%eax
  51:   89 45 fc                mov    %eax,-0x4(%ebp)
  54:   8b 45 fc                mov    -0x4(%ebp),%eax
  57:   c9                      leave  
  58:   c3                      ret    

你知道 | 运算符吗?(如果你使用内联汇编,我接受,只是指出你不一定必须这样做。) - DevSolar
当然,我知道位运算符。我只是想在汇编中尝试一些位操作。 - balajimc55
1
只是想确保一下。你不会相信有些人会固执地想到什么事情。;-) - DevSolar
1个回答

4

如何在asm中使用bts指令

在你的代码中,这行是:

bts %0, %%eax;  

应该替换为:

bts %%eax, %0;

解释

给定一般形式 asm("代码": 输出: 输入: 破坏) GCC 用冒号后的寄存器替换了“代码”中的 %0、%1 和 %2 表示传入的参数。BTS 的定义是第一个操作数是位字符串,第二个操作数是位索引。所以解决方案似乎是:像你在代码中所做的那样,使用 bts %0, %1。然而,这不是 bts 的工作方式:bts 希望地址作为第二个操作数,要设置的位作为第一位,所以应该是 bts %1, %0。请查看正确的用法此处

更好的解决方案

虽然你的代码在进行建议的更正后可以工作,但有更好的选择,如下所示:

uint32_t set_bit_assembly2(uint32_t x, uint32_t n)
{
    asm( "bts %1,%0"
         :"+r"(x)
         :"r"(n)
        );
    return x;
}

正如评论中@DavidWohlferd指出的那样,由于bts指令将读写x,因此我们应该使用"+r"。

此外,可以通过使用符号名称来提高可读性:

asm( "bts %[bit],%[value]"
     : [value] "+rm"(value) 
     : [bit] "r"(bit)
     :"cc");

另一种可能性是(请参见此帖子):
uint32_t set_bit_assembly3(uint32_t x, uint32_t n)
{
    asm( "bts %1,%0": "+rm"(x) : "r"(n));
    return x;
}

更多阅读:

这个页面可能会对想要使用bts的人非常有用:http://lxr.free-electrons.com/source/arch/x86/include/asm/bitops.h#L41

在这篇帖子中,Peter Cordes解释了为什么在内存操作数上使用bts会严重影响性能。


1
看看你的第一个例子,不应该是"+&r"吗?甚至可以是"+&rm"?而且由于这会影响标志位,你应该加上 "cc" 的 clobber。就我个人而言,我发现使用符号名称更清晰。所以更像这样:asm( "bts %[bit],%[value]": [value] "+&rm"(value) : [bit] "r"(argc):"cc"); - David Wohlferd
1
@balajimc55 给定一般形式 asm(“code”:outputs:inputs:clobbers),GCC 用冒号后的参数寄存器替换了“code”中的%0,%1和%2。 BTS 的定义说明第一个操作数是位字符串,第二个是位索引。因此,解决方案似乎是:像您在代码中所做的那样bts%0,%1。但是,这不是 bts 的工作方式:bts 要求将地址作为第二个操作数,并将位设置为第一位,因此:bts%1,%0。请参见 这里 (http://lxr.free-electrons.com/source/arch/x86/include/asm/bitops.h)。 - terence hill
1
哦,另外,bts 可以使用立即操作数,所以位数约束应该是 ri。否则,当内联到使用编译时常量计数的调用者时,gcc 将不得不单独地将计数移动到寄存器中。首先使用正确的约束,而不是留下 =&r,然后在下一段中说这是错误的。 - Peter Cordes
1
我认为内联汇编的解释还存在一些小问题。首先,'&'实际上并不是必需的。只使用'+'就足以将寄存器标记为“读/写”。'&'表示"early clobber",仅在有多个汇编指令时才会出现。在这里是安全的,但不会改变编译器的行为。请参阅https://gcc.gnu.org/onlinedocs/gcc/Modifiers.html获取官方解释。 - Nathan Kurz
1
第二个问题更大。除非您可以保证n <= 31,否则您不希望'r'和'm'都作为备选约束条件。当用存储器操作数作为目的地时,具有比寄存器宽度更大的位偏移量的BT函数的行为完全不同。寄存器版本执行模数运算,但存储器版本则不执行。请参见此处的表3-2:http://www.intel.com/content/dam/www/public/us/en/documents/manuals/64-ia-32-architectures-software-developer-instruction-set-reference-manual-325383.pdf。当Peter说“疯狂的CISC语义”时,他真的是这个意思! - Nathan Kurz
显示剩余13条评论

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