首页 > 解决方案 > 如何为 gcc 内联 asm 获取 64 位整数的低 32 位和高 32 位?(ARMV5平台)

问题描述

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

如何将较低或较高的 32 位 64 个变量传递给 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;
}

标签: cgccassemblyarminline-assembly

解决方案


您不需要也不应该为此使用内联汇编。编译器甚至可以做得更好smull,并使用smlal一条指令进行乘法累加:

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

它编译(在Godbolt-O3 -mcpu=arm10e编译器资源管理器上使用 gcc8.2)到这个 asm:(ARM10E 是我从维基百科列表中挑选的 ARMv5 微架构)

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

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

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


如果您坚持要自己动手并使用内联汇编:

或者在其他说明的一般情况下,可能存在您想要这个的情况。

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

在 ARMv6 及更高版本中取消了此限制(请参阅此 Keil 文档)“ Rn 必须与 ARMv6 之前的体系结构中的 RdLo 和 RdHi 不同”,但为了 ARMv5 兼容性,您需要确保编译器在填写内联时不会违反此规定-asm 模板。

当面向 32 位平台时,优化编译器可以优化将 32 位 C 变量组合成 64 位 C 变量的移位/或。它们已经将 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;
}

不幸的是,early-clobber 意味着我们总共需要 6 个寄存器,但 ARM 调用约定只有 6 个 call-clobbered 寄存器(r0..r3、lr 和 ip(又名 r12))。其中之一是 LR,它有返回地址,所以我们不能丢失它的值。当内联到已经保存/恢复多个寄存器的常规函数​​中时,可能没什么大不了的。

再次来自 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)约束并使用修饰符来选择%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 保存/恢复 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 位整数的一半似乎更有效。这显然是一个错过的优化错误。


推荐阅读