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

时间:2018-12-28 15:42:48

标签: c gcc assembly arm inline-assembly

我在armv5te平台上有一个项目,我必须重写一些功能并使用汇编代码来使用增强型DSP指令。 我对累加器使用了很多int64_t类型,但是我不知道如何将其传递给臂指令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;
}

1 个答案:

答案 0 :(得分:4)

您不需要也不应使用嵌入式asm。编译器的性能甚至比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)编译为以下asm:(ARM10E是我选择的from Wikipedia's list的ARMv5微体系结构)

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

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

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


如果您坚持用脚射击并使用嵌入式asm:

或者在通常情况下还有其他说明,在某些情况下,您可能会想要这样做。

首先,请注意,不允许smull输出寄存器与第一个输入寄存器重叠,因此,您必须将此告知编译器。对输出的早期限制操作数将告诉告诉编译器在这些寄存器中不能输入的技巧。我看不出一种告诉编译器第二输入可以与输出在同一寄存器中的干净方法。

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

优化编译器可以优化将32位C变量组合为64位C变量的shift / OR,以32位平台为目标。他们已经将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,它具有寄信人地址,因此我们不会失去其价值。内联到已经保存/恢复多个寄存器的常规函数​​中,可能没什么大不了的。

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