GCC内联ASM X86 CPU标志作为输入依赖性

gcc inline asm x86 CPU flags as input dependency

本文关键字:输入 输入依赖 依赖性 标志 内联 ASM X86 CPU GCC      更新时间:2023-10-16

我想为两个具有溢流检测的16位整数创建一个函数。我有便携式c编写的通用变体。但是通用变体对于x86目标并不是最佳的,因为执行添加/sub/etc时,CPU内部计算溢出标志。当然,有__builtin_add_overflow(),但就我而言,它会生成一些样板。因此,我编写以下代码:

#include <cstdint>
struct result_t
{
    uint16_t src;
    uint16_t dst;
    uint8_t  of;
};
static void add_u16_with_overflow(result_t& r)
{
    char of, cf;
    asm (
        " addw %[dst], %[src] " 
        : [dst] "+mr"(r.dst)//, "=@cco"(of), "=@ccc"(cf)
        : [src] "imr" (r.src) 
        : "cc"
        );
    asm (" seto %0 " : "=rm" (r.of) );
}
uint16_t test_add(uint16_t a, uint16_t b)
{
    result_t r;
    r.src = a;
    r.dst = b;
    add_u16_with_overflow(r);
    add_u16_with_overflow(r);
    return (r.dst + r.of); // use r.dst and r.of for prevent discarding
}

我已经玩过https://godbolt.org/g/2mlf55(GCC 7.2 -O2 -STD = C 11),结果

test_add(unsigned short, unsigned short):
  seto %al 
  movzbl %al, %eax
  addw %si, %di 
  addw %si, %di 
  addl %esi, %eax
  ret

所以,重新排序seto %0。GCC似乎认为asm()语句之间没有依赖性。和" CC" Clobber对标志依赖性没有任何影响。

我不能使用volatile,因为seto %0或整个功能可以(如果结果(或结果的某些部分)不使用)。

i可以添加R.DST:asm (" seto %0 " : "=rm" (r.of) : "rm"(r.dst) );的依赖关系,并且不会发生重新排序。但这不是"正确的事情",编译器仍然可以在addseto语句之间插入一些代码更改标志(但不能更改r.dst)。

是否可以说"此ASM()语句更改某些CPU标志"answers"此asm()使用一些CPU标志"来依赖语句和防止重新排序?

我还没有查看__builtin_add_overflow的GCC输出,但是有多糟糕?@David使用它的建议,以及https://gcc.gnu.org/wiki/dontuseinlinelineasm通常很好,尤其是当您担心这将如何优化时。asm击败了不断的传播和其他一些事情。

另外,如果您要使用ASM,请注意ATT语法是add %[src], %[dst]操作数订单。有关详细信息,请参阅标签Wiki,除非您始终使用-masm=intel构建代码。

是否可以说"此ASM()语句更改一些CPU标志"answers"此ASM()使用一些CPU标志"来依赖语句和防止重新排序?

否。将标志耗电指令(seto)放入与标志产生指令的同一asm块中。asm语句可以随意具有许多输入和输出操作数,仅受寄存器分配难度的限制(但是多个内存输出可以使用相同的基本寄存器与不同的偏移量)。无论如何,包含add的语句上的额外写入输出不会导致任何效率低下。

我要建议您要从一项指令中多个标志输出,请使用lahf从标志加载AH。但这不包括其他条件代码。这通常是不方便的,似乎是一个糟糕的设计选择,因为在eflags/rflags的低8位中有一些未使用的保留位,因此可以与CF,SF,SF,ZF,PF和AF一起使用。但是由于事实并非如此,因此setc seto可能比pushf/Reload更好,但值得考虑。


即使有用于标志输入的语法(例如有标志输出),从让GCC插入其一些自己的某些非flag-modifying指令中,就会有很少的收益。(例如leamov)在您的两个单独的asm语句之间。

您不希望它们重新排序或其他任何东西,因此将它们放在同一ASM语句中最有意义。即使在订购CPU上,add也是低潜伏期,因此在其之后放置依赖指令并不是一个大的瓶颈。


和顺便说一句,如果溢出是正常发生的错误条件,则jcc可能会更有效。但是不幸的是,GNU C asm goto不支持输出操作数。您可以在存储器中进行指针输入并修改dst(并使用"memory" Clobber),但是强迫商店/重新加载远远超过使用setcseto来产生编译器生成的test/jnz

如果您也不需要输出,则可以在return truereturn false语句上放置C标签if()。例如请查看Linux的工作方式:(在我发现的这两个示例中,请使用额外的复杂因素):在启动后检查一次CPU功能后,设置用于修补代码,或者在arch_static_branch中使用跳跃表的部分。)