如何为 gcc 内联 asm 获取 64 位整数的低 32 位和高 32 位? (ARMV5平台)

How to get lower and higher 32 bits of a 64-bit integer for gcc inline asm? (ARMV5 platform)

我有一个在armv5te平台上的项目,我要重写一些功能,用汇编代码来使用增强DSP指令。 我为累加器使用了很多 int64_t 类型,但我不知道如何将它传递给 arm 指令 SMULL (http://www.keil.com/support/man/docs/armasm/armasm_dom1361289902800.htm).

如何将 64 个变量的低 32 位或高 32 位传递给 32 位寄存器? (我知道,我可以使用中间变量 int32_t,但它看起来不太好)。

我知道,那个编译器会为我做这件事,但我只是写了一个小函数作为例子。

int64_t testFunc(int64_t acc, int32_t x, int32_t y)
{
   int64_t tmp_acc;

   asm("SMULL %0, %1, %2, %3"
      : "=r"(tmp_acc), "=r"(tmp_acc) // no idea how to pass tmp_acc;
      : "r"(x), "r"(y)
      );

return tmp_acc + acc;
}

您不需要也不应该为此使用内联汇编。编译器可以做的比smull还好,用smlal一条指令乘累加:

int64_t accum(int64_t acc, int32_t x, int32_t y) {
    return acc + x * (int64_t)y;
}

编译 (with gcc8.2 -O3 -mcpu=arm10e on the Godbolt compiler explorer) to this asm: (ARM10E is an ARMv5 microarchitecture I picked from Wikipedia's list)

accum:
    smlal   r0, r1, r3, r2        @, y, x
    bx      lr  @

作为奖励,这个纯 C 还可以为 AArch64 高效编译。

https://gcc.gnu.org/wiki/DontUseInlineAsm


如果你坚持搬起石头砸自己的脚并使用内联汇编:

或者在使用其他说明的一般情况下,可能会有您需要的情况。

首先,请注意 smull 输出寄存器不允许与第一个输入寄存器重叠,因此您必须将此告知编译器。对输出操作数的破坏约束将起到告诉编译器它不能在这些寄存器中输入的技巧。我没有看到一个干净的方法来告诉编译器第二个输入可以与输出在同一个寄存器中。

此限​​制在 ARMv6 及更高版本中解除(参见 this Keil documentation)“Rn 必须不同于 ARMv6 之前架构中的 RdLo 和 RdHi”,但为了 ARMv5 兼容性你需要确保编译器在填写你的内联汇编模板时不会违反这一点。

当针对 32 位平台时,优化编译器可以优化将 32 位 C 变量组合成 64 位 C 变量的 shift/OR。他们已经将 64 位变量存储为一对寄存器,并且在正常情况下可以弄清楚在 asm 中没有实际的工作要做。

因此您可以将一个 64 位输入或输出指定为一对 32 位变量。

#include <stdint.h>

int64_t testFunc(int64_t acc, int32_t x, int32_t y)
{
   uint32_t prod_lo, prod_hi;

   asm("SMULL %0, %1, %2, %3"
      : "=&r" (prod_lo), "=&r"(prod_hi)  // early clobber for pre-ARMv6
      : "r"(x), "r"(y)
      );

    int64_t prod = ((int64_t)prod_hi) << 32;
    prod |= prod_lo;        // + here won't optimize away, but | does, with gcc
    return acc + prod;
}

不幸的是,早期破坏意味着我们总共需要 6 个寄存器,但 ARM 调用约定只有 6 个调用破坏寄存器 (r0..r3, lr, and ip (aka r12))。其中之一是 LR,它具有 return 地址,因此我们不能丢失它的值。内联到已经 saves/restores 多个寄存器的常规函数​​中可能没什么大不了的。

再次from Godbolt:

@ gcc -O3 output with early-clobber, valid even before ARMv6
testFunc:
    str     lr, [sp, #-4]!    @,         Save return address (link register)
    SMULL ip, lr, r2, r3    @ prod_lo, prod_hi, x, y
    adds    r0, ip, r0      @, prod, acc
    adc     r1, lr, r1        @, prod, acc
    ldr     pc, [sp], #4      @          return by popping the return address into PC


@ gcc -O3 output without early-clobber (&) on output constraints:
@ valid only for ARMv6 and later
testFunc:
    SMULL r3, r2, r2, r3    @ prod_lo, prod_hi, x, y
    adds    r0, r3, r0      @, prod, acc
    adc     r1, r2, r1        @, prod, acc
    bx      lr  @

或者您可以使用 "=r"(prod64) 约束并使用修饰符 select 您得到 %0 的一半。 不幸的是,gcc 和由于某种原因,clang 发出效率较低的 asm,从而节省了更多寄存器(并保持 8 字节堆栈对齐)。 gcc 是 2 而不是 1,clang 是 4 而不是 2。

// using an int64_t directly with inline asm, using %Q0 and %R0 constraints
// Q is the low half, R is the high half.
int64_t testFunc2(int64_t acc, int32_t x, int32_t y)
{
   int64_t prod;    // gcc and clang seem to want more free registers this way

   asm("SMULL %Q0, %R0, %1, %2"
      : "=&r" (prod)         // early clobber for pre-ARMv6
      : "r"(x), "r"(y)
      );

    return acc + prod;
}

再次用 gcc 编译 -O3 -mcpu=arm10e。 (clang saves/restores 4 个寄存器)

@ gcc -O3 with the early-clobber so it's safe on ARMv5
testFunc2:
    push    {r4, r5}        @
    SMULL r4, r5, r2, r3    @ prod, x, y
    adds    r0, r4, r0      @, prod, acc
    adc     r1, r5, r1        @, prod, acc
    pop     {r4, r5}  @
    bx      lr  @

因此出于某种原因,使用当前的 gcc 和 clang 手动处理 64 位整数的一半似乎更有效。这显然是一个遗漏的优化错误。