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

问题描述 投票:2回答:1

我想创建一个函数,用于添加两个带有溢出检测的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标志”为语句之间的依赖和防止重新排序?

c++ gcc assembly constraints
1个回答
2
投票

我没有看过gcc的__builtin_add_overflow输出,但它有多糟糕? @David's建议使用它,而https://gcc.gnu.org/wiki/DontUseInlineAsm通常是好的,特别是如果你担心这将如何优化。 asm击败了不断传播和其他一些东西。

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

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

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

我打算建议如果你想要一条指令的多个标志输出,使用LAHF从FLAGS加载AH。但这不包括OF,只包括其他条件代码。这通常很不方便,因为有some unused reserved bits in the low 8 of EFLAGS/RFLAGS,所以看起来像是一个糟糕的设计选择,因此OF可能在CF,SF,ZF,PF和AF中处于低8。但由于情况并非如此,setc + seto可能比pushf / reload更好,但这值得考虑。


即使有flag-input的语法(就像flag-output那样),让gcc在你的两个单独的插入之间插入一些自己的非标志修改指令(如leamov)也几乎没有什么好处。 asm声明。

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


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

如果你还不需要输出,你可以将C标签放在return truereturn false语句上,(在内联之后)将你的代码转换为jcc到编译器想要布置if()分支的地方。例如看看Linux是如何做到的:(在我发现的这两个例子中有更复杂的因素):setting up to patch the code在启动后检查一次CPU功能,或者在arch_static_branch中有一个跳转表的部分。)

© www.soinside.com 2019 - 2024. All rights reserved.