繁体   English   中英

gcc 内联 asm x86 CPU 标志作为输入依赖项

[英]gcc inline asm x86 CPU flags as input dependency

我想创建一个函数,通过溢出检测将两个 16 位整数相加。 我有用便携式 c 编写的通用变体。 但是通用变体对于 x86 目标不是最佳的,因为 CPU 在执行 ADD/SUB/etc 时在内部计算溢出标志。 当然,有__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或整个函数。

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

有没有办法说“这个 asm() 语句更改了一些 cpu 标志”和“这个 asm() 使用一些 cpu 标志”用于语句之间的依赖关系并防止重新排序?

我没有查看__builtin_add_overflow gcc 输出,但它有多糟糕? @David建议使用它, https://gcc.gnu.org/wiki/DontUseInlineAsm通常很好,特别是如果您担心这将如何优化。 asm打败了持续传播和其他一些事情。

另外,如果您打算使用 ASM,请注意语法是add %[src], %[dst]操作数顺序。 有关详细信息,请参阅标签 wiki ,除非您总是要使用-masm=intel构建代码。

有没有办法说“这个 asm() 语句更改了一些 cpu 标志”和“这个 asm() 使用一些 cpu 标志”用于语句之间的依赖关系并防止重新排序?

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

我打算建议,如果您想从一条指令中输出多个标志,请使用 LAHF 从 FLAGS 加载 AH。 但这不包括 OF,仅包括其他条件代码。 这通常很不方便,而且似乎是一个糟糕的设计选择,因为在 EFLAGS/RFLAGS 的低 8 位中一些未使用的保留位,因此 OF 可能与 CF、SF、ZF、PF 和 AF 一起位于低 8 位。 但由于事实并非如此, setc + seto可能比pushf / reload 更好,但这值得考虑。


即使是为标志,输入语法(如存在为标志输出),将有很少的增益从让GCC插入了一些自己的非旗修改指令(像leamov你的两个独立之间) asm语句。

你不希望它们重新排序或任何东西,所以把它们放在同一个 asm 语句中是最有意义的。 即使在有序 CPU 上, add的延迟也很低,因此在它之后放置相关指令并不是一个大瓶颈。


顺便说一句,如果溢出是一种不正常发生的错误情况,则jcc可能会更有效。 但不幸的是 GNU C asm goto不支持输出操作数。 您可以获取指针输入并修改内存中的dst (并使用"memory" clobber),但强制存储/重新加载比使用setcseto为编译器生成的test / jnz生成输入更jnz

如果您还不需要输出,您可以将 C 标签放在return truereturn false语句上,这(内联后)会将您的代码转换为 jcc 到编译器想要布置if()分支的任何位置if() . 例如,看看 Linux 是如何做到的:(在我发现的这两个示例中有额外的复杂因素):在启动时检查一次 CPU 功能后设置修补代码,或者在arch_static_branch包含跳转表部分的arch_static_branch 。)

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM