使用条件标志作为GNU C内联汇编输出

8
我正在处理一些代码,其中将条件标志从内联汇编块中输出并将其用作在调用C代码中进行分支的条件非常理想。我不想存储标志(这将是无用且低效的;已经有更有效的方法来实现结果),而是直接使用标志。是否有任何方法可以使用GNU C内联汇编约束来实现此目的?我对适用于多个指令集体系结构的方法感兴趣,旨在将其与体系结构的LL / SC样式原子产生的条件标志一起使用。当然,另一个明显的用例(与我所做的分开)将允许外部C代码根据内联汇编中操作的进位标志进行分支。

如果现在对你没有帮助,你可能会发现这次讨论有趣。并且让我再为内嵌函数做一个广告。虽然似乎内联汇编提供了很好的解决方案,但是你内联代码周围的代码可能会付出性能惩罚,这比你从汇编获得的任何好处都要花费更高昂。仅此而已。 - David Wohlferd
内建函数确实有其优点,但关键在于细节。在某些目标平台上,它们会生成非常糟糕的代码。在 ARM 上,它们会产生 dmb sy 屏障(与总线上的所有外部硬件同步),而不是期望的 dmb ish(仅与 CPU 核心同步)。在其他一些平台上,它们会生成库调用而不是内联代码,或者在使用旧的 -march 时生成与某些较新的目标不兼容的代码,而不是允许我们需要针对这些目标运行时分支多个变体。 - R.. GitHub STOP HELPING ICE
现代编译器非常擅长将内联汇编和 C 代码整合在一起,只要约束条件写得好。目前,我将整个 lls/sc 序列放在一个 asm 块中,这需要针对每个目标的原子操作分别编写汇编语言。如果我能够重构为“ll”和“sc”部分分别放在不同的 asm 块中,并在它们之间使用 C 代码进行操作,那么我就可以获得相同的效率,而不会有任何针对特定目标的重复;所有 ll/sc 类型的目标都可以使用相同的 C 代码,只有 ll 和 sc 部分不同。 - R.. GitHub STOP HELPING ICE
3个回答

7

从GCC6开始,在x86平台上,你实际上可以使用"=@ccCOND"作为输出(其中COND是任何有效的x86条件码)。

示例最初来自此处,经过David的建议进行了清理:

int variable_test_bit(long n, volatile const unsigned long *addr)
{
    int oldbit;
    asm volatile("bt %[value],%[bit]"
                 : "=@ccc" (oldbit)
                 : [value] "m" (*addr), [bit] "Jr" (n));
    return oldbit;
}

在使用此功能之前,您应该测试是否定义了__GCC_ASM_FLAG_OUTPUTS__
文档请参考https://gcc.gnu.org

我不确定在这里使用I是否正确。在某些平台上,长整型可以是64位,而I仅适用于值0..31。我也不清楚为什么我们需要去除constvolatile。它是一个输入参数,所以const不应该是问题。它是一个m,所以volatile是隐含的。我还可能会添加符号名称([bit][value])。这不影响功能,但对于我们习惯英特尔格式的人来说可能更清晰。 - David Wohlferd
@DavidWohlferd 我承认我没有成功让gcc在将oldbit与其他两个整数相加时生成一个adc指令... - chtz
1
我不会期望它执行adc。无论如何,那不是OP的要求。他想要跳转。你的代码做到了(https://godbolt.org/g/gA2qgL)。 - David Wohlferd
如果您只想允许0..63,您应该在寄存器中请求*addrbt的内存操作形式要慢得多,特别是使用寄存器源(Haswell上的10个uops与reg,reg相比为1)。 此外,寄存器源将允许访问除操作数告诉gcc的qword之外的内存,因此您确实需要类似于此的内容[value]“m”(*(const unsigned long (*) [])addr)告诉gcc它可能会访问相对于addr的任何位置。 - Peter Cordes
简而言之,带有内存操作数的bt*指令具有疯狂的CISC位串语义:http://felixcloutier.com/x86/BT.html,并且对于寄存器源,这是通过微码处理的。最好让编译器将其加载到寄存器中。 - Peter Cordes

1
我有一个部分解决方案,但我不是很喜欢它,因为它需要将分支指令放在asm中,并且需要一个非常丑陋的GCC特性,其他“GNU C兼容”的编译器可能不支持:asm goto。然而,它确实允许消除asm外部的分支。思路是:
static inline int foo(...)
{
    __asm__ goto ( " .... ; cond_jmp %l[ret0]" : : "r"(...) ... 
                   : "clobbers" : ret0 );
    return 1;
ret0:
    return 0;
}

当嵌入到调用者中并执行if (foo(...)) ... else ...时,汇编块中的条件跳转指向else分支,即使在抽象机器级别上涉及返回值。

Linux内核在多个地方使用这种结构,而且显然当你使用if( foo(...) ) {} else {}时,它确实会进行优化。编译器实际上从未将10放入寄存器中,只是将asm goto用作if分支。但是,你强制选择哪一侧是执行的,哪一侧是跳过的。可能gcc6条件代码输出至少会进行同样的优化,除非你的代码具有早期退出条件(因此条件分支不是你的汇编语句中的最后一条指令)。在这种情况下,这仍然很有用,+1。 - Peter Cordes

-1

很遗憾,GCC不支持在asm语句之外访问条件标志。如果您不想设置值,则必须将条件分支移动到asm语句中。这意味着要么使用您已经发现的asm goto labels,要么也将分支目标带入您的asm语句。

您还可以检查一下GCC的旧式__sync atomic builtins或较新的memory model based atomics是否提供了您所使用的原子指令所需的功能。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接