c - GNU 内联汇编 : same register for different output operands allowed?

标签 c gcc inline-assembly

我用 C 代码和简短的内联汇编语句编写了一个小函数。
在内联汇编语句中,我需要 2 个“临时”寄存器来加载和比较一些内存值。
为了允许编译器选择“最佳临时寄存器”,我想避免对这些临时寄存器进行硬编码(并将它们放入破坏列表中)。 相反,我决定为此目的在周围的 C 函数中创建 2 个局部变量。我使用“=r”将这些局部变量添加到内联 asm 语句的输出操作数规范中,然后将它们用于加载/比较目的。
这些局部变量不在 C 函数的其他地方使用,并且(也许是因为这个事实)编译器决定将相同的寄存器分配给两个相关的输出操作数,这使得我的代码无法使用(比较始终为真)。

编译器是否允许对不同的输出操作数使用重叠寄存器,或者这是一个编译器错误(我倾向于将其视为一个错误)?
我只找到了有关早期破坏者的信息,这些破坏者防止输入和输出的寄存器重叠......但没有仅针对输出操作数的声明。

解决方法是初始化临时变量并在输出操作数规范中使用“+r”而不是“=r”。但在这种情况下,编译器会发出我想避免的初始化指令。
是否有任何干净的方法可以让编译器选择不相互重叠的最佳寄存器,仅用于“内部内联汇编使用”?

非常感谢!

P.S.:我使用支持“GNU 内联汇编”的“非 GNU”编译器为一些“异国情调”的目标进行编码。
P.P.S.:我也不明白下面的示例中为什么编译器不生成“int eq=0;”的代码(例如“mov d2,0”)。也许我完全误解了“=”约束修饰符?

完全无用且愚蠢下面的示例只是为了说明(重点)问题:

int foo(const int *s1, const int *s2)
{
    int eq = 0;
#ifdef WORKAROUND
    int t1=0, t2=1;
#else
    int t1, t2;
#endif

    __asm__ volatile(
        "ld.w  %[t1], [%[s1]]   \n\t"
        "ld.w  %[t2], [%[s2]]   \n\t"
        "jne   %[t1], %[t2], 1f \n\t"
        "mov   %[eq], 1         \n\t" 
        "1:"
        : [eq] "=d" (eq),
          [s1] "+a" (s1), [s2] "+a" (s2),
#ifdef WORKAROUND
          [t1] "+d" (t1), [t2] "+d" (t2)
#else
          [t1] "=d" (t1), [t2] "=d" (t2)
#endif
    );

    return eq;
}

在创建的汇编中,编译器使用寄存器“d8”作为操作数“t1”和“t2”:

foo:
    ; 'mov d2, 0' is missing
    ld.w  d8, [a4]  ; 'd8' allocated for 't1'
    ld.w  d8, [a5]  ; 'd8' allocated for 't2' too!
    jne   d8, d8, 1f 
    mov   d2, 1         
1:
    ret16

使用“-DWORKAROUND”进行编译:

foo:
    ; 'mov d2, 0' is missing
    mov16 d9,1
    mov16 d8,0

    ld.w  d9, [a5]   
    jne   d8, d9, 1f 
    mov   d2, 1         
1:
    ret16

native 的 EABI:

  • 返回寄存器(非指针/指针):d2, a2
  • 非指针参数:d4..d7
  • 指针参数:a4..a7

最佳答案

我认为这是您的编译器中的一个错误。

如果它说它支持“GNU 内联汇编”,那么人们会期望它遵循 GCC,其 manual是最接近正式规范的东西。现在 GCC 手册似乎没有明确说明“输出操作数不会彼此共享寄存器”,但正如 o11c 提到的那样,他们确实建议将输出操作数用于暂存寄存器,如果它们可以共享寄存器,那就行不通。

可能比您的更有效的解决方法是在内联汇编后添加第二个“使用”两个输出的虚拟汇编语句。希望这能让编译器相信它们可能是不同的值,因此需要单独的寄存器:

    int t1, t2;
    __asm__ volatile(" ... code ..."
          : [t1] "=d" (t1), [t2] "=d" (t2) : ...);
    __asm__ volatile("" // no code
          : : "r" (t1), "r" (t2));

幸运的话,这将避免为不必要的初始化等生成任何额外的代码。

另一种可能性是对特定的暂存寄存器进行硬编码并将其声明为已损坏。它为寄存器分配器留下了较少的灵活性,但根据周围的代码和编译器的智能程度,它可能不会产生很大的差异。

关于c - GNU 内联汇编 : same register for different output operands allowed?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/71831841/

相关文章:

c - GCC 将包含程序集的静态库插入到动态库中

c++ - C 套接字 : Echo server bad reply

c - 后缀或操作数对于 gcc 的 `move' 无效

ubuntu - 在 SSH 中生成文件后无法启动文件

gcc - 如何使用 GCC C 代码与 RISC-V CSR 交互?

linux - 在内联 GNU 汇编器中获取字符串长度

c - 在尝试释放()之前确定结构成员是否具有有效数据

计算每个单词在给定句子中出现的次数

c - 128乘法和除法的内在函数

汇编中的C变量使用,如何在arm aarch64中选择32位操作数