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

C++ gcc内联asm x86 CPU标志作为输入依赖项,c++,gcc,assembly,constraints,att,C++,Gcc,Assembly,Constraints,Att,我想创建一个函数,用于添加两个带溢出检测的16位整数。我有一个用c写的通用变体。但是对于x86目标来说,通用变量不是最佳的,因为当执行ADD/SUB/etc时,CPU会在内部计算溢出标志。当然,有\uuu内置\uu ADD\u overflow(),但在我的例子中,它会生成一些样板文件。 因此,我编写了以下代码: #include <cstdint> struct result_t { uint16_t src; uint16_t dst; uint8_t

我想创建一个函数,用于添加两个带溢出检测的16位整数。我有一个用c写的通用变体。但是对于x86目标来说,通用变量不是最佳的,因为当执行ADD/SUB/etc时,CPU会在内部计算溢出标志。当然,有
\uuu内置\uu ADD\u 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
}
因此,
seto%0
被重新排序。似乎gcc认为两个后续的
asm()
语句之间没有依赖关系。“cc”clobber对标志依赖性没有任何影响

我不能使用
volatile
,因为如果不使用结果(或结果的某一部分),则可以(并且必须)优化整个函数

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


有没有办法说“this asm()statement change some cpu flags”和“this asm()use some cpu flags”来表示语句之间的依赖关系并防止重新排序?

我还没有看过gcc的输出,但它有多糟糕?建议使用它,通常是好的,特别是如果你担心这将如何优化<代码>asm
会阻止持续传播和其他一些事情

另外,如果要使用ASM,请注意语法是
add%[src],%[dst]
operandom order。有关详细信息,请参阅,除非您总是使用
-masm=intel
构建代码

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

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

我想建议,如果您希望从一条指令中输出多个标志,请使用LAHF从标志中加载AH。但这不包括OF,只包括其他条件代码。这通常是不方便的,似乎是一个糟糕的设计选择,因为有,所以OF可能与CF、SF、ZF、PF和AF一起处于低8。但由于情况并非如此,
setc
+
seto
可能比
pushf
/reload更好,但这值得考虑


即使有标志输入语法(就像标志输出语法一样),让gcc在两个单独的
asm
语句之间插入一些自己的非标志修改指令(如
lea
mov
)也不会有什么好处

您不希望对它们进行重新排序或其他任何操作,因此将它们放在同一个asm语句中最有意义。即使在顺序CPU上,
add
的延迟也很低,因此在它后面直接放置一条相关指令并不是一个很大的瓶颈


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


如果不需要输出,可以在
return true
return false
语句上放置C标签,这(内联后)会将代码转换为jcc,以便编译器将
If()
的分支放在任何地方。e、 g.看看Linux是如何做到的:(在这两个例子中,我发现了额外的复杂因素):在启动时检查一次CPU功能,或者在某个部分中检查一个跳转表。)

我还没有看完整的问题,但是因为您使用的是GCC 7.x,所以可以使用
=%cc
约束来访问特定的标志。在您的情况下,
=%cco
。看见否则,您可以将
seto
放在具有适当输出约束的第一个扩展asm语句中。@MichaelPetch,例如GCC 7.x(默认为godbolt.org),5.x和6.x也是我的目标。所以我不能使用
=%cco
。如果我在第一个asm语句中添加
seto
,当不使用“overflow”时,它不会被丢弃,从而导致更大的代码和更差的性能(我的目标是最大化性能)。另一个观察结果。在AT&T语法中,src是第一位的,destination是第二位的(这与Intel语法相反)。可以说溢出检查总是合适的。您考虑的是从第一个16位溢出到第二个16位。但第二个16位值也可能(可以想象)溢出。不检查明显的错误情况通常是个坏主意。当事情有时崩溃时,你保存的任何性能都会丢失。此外,您可能希望对溢出使用
“qm”
,而不是
rm
。即使使用7.x,也无法将标志作为输入约束。我将再次查看
\u内置\u添加\u溢出
,并确保您在构建时启用了优化。
test_add(unsigned short, unsigned short):
  seto %al 
  movzbl %al, %eax
  addw %si, %di 
  addw %si, %di 
  addl %esi, %eax
  ret