需要帮助理解位复位功能

时间:2015-05-21 06:59:55

标签: c assembly x86 bit-manipulation inline-assembly

我是C的新手,发现很难理解这里的装配级别操作。有人可以帮忙吗?

/**
 * Input: bitmap - u32bits*
 *        bitpos - position of the bit to be reset (range 0-31)
 * return: old value of the bit (0 if unset, 1 if set)
 **/
static inline u32bits resetbit(u32bits *bitmap, u32bits bitpos)
{
     u32bits oldbit;
    __asm__ __volatile__ (
         "btr    %2, (%1)\n" /* bit test and reset */
         "sbbl   %0, %0\n"   /* return the previous value*/
        : "=r"(oldbit)      /* "0" output  parameter */
        :                   /* input parameters */
           "0"(bitmap),      /* "1" */
           "r"(bitpos)       /* "2" */
        : "%cc", "memory"   /* clobbered registers */
        );
    return oldbit;
}

2 个答案:

答案 0 :(得分:2)

BTR指令将所选位存储在EFLAGS.CF中,并清除源操作数中的该位(bitmap)。

接下来,执行SBB,并将oldbit指定为源和目标。 SBB将从目标操作数中减去CF标志的来源和值。基本上,在这里,我们正在做: oldbit = oldbit - oldbit - CF。如您所见,如果CF0(请记住,CFBTR指令设置,具体取决于{{1}中是否设置了指定的位}),bitmap将设置为oldbit,因为操作将有效: 0

否则oldbit = oldbit - oldbit - 0将被设置为oldbit(因为-1 = 1),其中所有位都已设置并且表示原始位也已设置。

答案 1 :(得分:1)

这是https://gcc.gnu.org/onlinedocs/gcc/Extended-Asm.html

这需要volatile"memory"破坏者,因为bitmap仅被指定为寄存器中的指针输入,而不是"+m"(*bitmap)内存操作数。但是请注意,bitpos可以在*bitmap之外进行索引,因此它实际上是在u32处访问bitmap[bitpos>>5]

不需要"cc"(条件代码)破坏者; x86内联汇编已经隐式破坏了条件代码。 "0"(bitmap)输入约束是“匹配”约束,这意味着bitmap输入必须与操作数0 "=r"(oldbit)处于同一寄存器中。这似乎没有必要,如果gcc只想使用"r"(bitmap),就可以做到这一点。但是也许他们想避免对输出寄存器的错误依赖:只有某些AMD CPU将sbb same,same视为仅取决于CF,而不取决于寄存器值。

您可以像这样不使用volatile来编写此代码,方法是使用读/写内存操作数,该操作数告诉编译器整个数组是输入,并且可以被修改。 (但是没有其他任何内存,因此它仍然可以将其他全局变量保留在寄存器中。)强制转换为指针数组和取消引用是一种hack,但(我官方认为)得到了GCC的支持。

我们也可以将GCC6语法用于asm的条件代码输出,而不用内联SBB。通常,您只想在此分支,或将其用作cmovccsetcc的谓词,因此必须使用编译器生成的{{将0 / -1结果转换回EFLAGS 1}}指令。

test

如果我使用typedef unsigned u32bits; #ifndef __GCC_ASM_FLAG_OUTPUTS__ #error flag outputs unsupported #endif static inline u32bits resetbit(u32bits *bitmap, u32bits bitpos) { u32bits oldbit; __asm__ ( "btrl %[bitidx], %[base]\n" /* bit test and reset */ : "=@ccc"(oldbit) // output = c condition (carry set) ,[base] "+m"( *(u32bits(*)[])bitmap ) // whole "array" of arbitrary length is a read/write input : [bitidx] "Kr"(bitpos) // signed imm8 or register : ); oldbit = -oldbit; // if you insist on a 0 / -1 result return oldbit; } 作为内存操作数,则gcc会假设asm之前的[base] "+m"( *bitmap )不是输入,并且可以将其延迟到asm之后。转换为指针到数组并解引用会告诉编译器整个数组或通过该指针可到达的任何内容都是对asm的输入。 (即使使用bitmap[4] = tmp;掩体,这对于不使用"memory"也不进行优化也很重要。但这也使我们避免使用asm volatile掩体。)准确描述{{ 1}}对编译器的声明几乎总是比使用"memory"更好,因为有时它可以优化。

请注意asm约束,该约束允许该位位置为立即数。如果位索引是内联后的较小编译时间常数,则这会导致更好的代码(但仍然可能不是最佳选择,而不是根本不使用内联汇编)。如果两个操作数都不是寄存器,则必须使用asm volatile来使操作数大小明确。

Compiles with gcc8.2 -O3 to a somewhat clunky version。 (但如果内联到"Kr"可以使btrl最佳化的东西中就可以了。)

if(resetbit(a,b))

但是clang7.0不支持-oldbit,但是clang9.0干线支持,并且在需要以这种方式返回整数时会自行选择使用SBB。

# gcc8.2 -O3 for x86-64 System V
resetbit(unsigned int*, unsigned int):
        btr   %esi, (%rdi)          # inline asm

        setc    %al                 # compiler generated
        movzbl  %al, %eax
        negl    %eax
        ret

这样的测试调用者可以内联它。 __GCC_ASM_FLAG_OUTPUTS__布尔返回0/1,无需使用# clang version 9.0.0 (trunk 354240) -O3 for x86-64 System V resetbit(unsigned int*, unsigned int): btrl %esi, (%rdi) # inline asm sbbl %eax, %eax # compiler generated retq

!!
neg

请注意,内存目标int test_smallconst(u32bits *bitmap) { return !!resetbit(bitmap, 125); } 非常慢

这是因为 的一种非常不寻常的行为,即将内存操作数作为位索引的基地址,而不是要寻址的实际内存双字。 (内存目标# clang9 trunk -O3 test_smallconst(unsigned int*): xorl %eax, %eax # compiler generate setup for setcc btrl $125, (%rdi) # inline asm setb %al # compiler generated retq 指令不会像对寄存器目标那样掩盖寄存器位索引。)在现代x86上,使用计算位索引的指令来模拟btr mem, reg可能会更快和带移位的面具。因此,用纯C编写代码可能是一个胜利。