为什么gcc(ARM)没有使用全局寄存器变量作为源操作数?

时间:2016-04-14 22:40:07

标签: c gcc assembly arm cpu-registers

这是一个c源代码示例:

register int a asm("r8");
register int b asm("r9");

int main() {
    int c;
    a=2;
    b=3;
    c=a+b;
    return c;
}

这是使用arm gcc交叉编译器生成的汇编代码:

$ arm-linux-gnueabi-gcc  -c global_reg_var_test.c -Wa,-a,-ad

...
mov     r8, #2
mov     r9, #3
mov     r2, r8
mov     r3, r9
add     r3, r2, r3
...

使用-frename-registers时,行为是相同的。 (更新。在我用-O3说过之前。)

所以问题是:为什么gcc添加第3和第4个MOV而不是'ADD R3,R8,R9'?

上下文:我需要在不重命名寄存器的模拟inorder cpu(gem5 arm minorcpu)中优化代码。

1 个答案:

答案 0 :(得分:2)

我采用了真实的例子(发表在评论中)和put it on the godbolt compiler explorercalc()中的主要低效率是src1src2是必须从内存加载的全局变量,而不是寄存器中传递的args。

我没有看main,只看calc

register int sum asm ("r4");
register int r asm ("r5");
register int c asm ("r6");
register int k asm ("r7");
register int temp1 asm ("r8");    // really?  you're using two global register vars for scratch temporaries?  Just let the compiler do its job.
register int temp2 asm ("r9");
register long n asm ("r10");
int *src1, *src2, *dst;

void calc() {
  temp1 = r*n;
  temp2 = k*n;

  temp1 = temp1+k;
  temp2 = temp2+c;

  // you get bad code for this because src1 and src2 are globals, not args passed in regs
  sum = sum + src1[temp1] * src2[temp2];
}

    # gcc 4.8.2 -O3 -Wall -Wextra -Wa,-a,-ad -fverbose-asm
    mla     r0, r10, r7, r6          @ temp2.9, n, k, c   @@ tmp = k*n + c
    movw    r3, #:lower16:.LANCHOR0  @ tmp136,
    mla     r8, r10, r5, r7          @ temp1, n, r, k     @@ temp1 = r*n + k
    movt    r3, #:upper16:.LANCHOR0  @ tmp136,
    ldmia   r3, {r1, r2}             @ tmp136,,           @@ load both pointers, since they're stored adjacently in memory
    mov     r9, r0                   @ temp2, temp2.9     @@ This insn is wasted: the first MLA should have had this as the dest
    ldr     r3, [r1, r8, lsl #2]     @ *_22, *_22
    ldr     r2, [r2, r9, lsl #2]     @ *_28, *_28
    mla     r4, r2, r3, r4           @ sum, *_28, *_22, sum
    bx      lr                       @

由于某种原因,其中一个整数乘法累加(mla)指令使用r8temp1)作为目标,但另一个指令写入r0 (临时注册),稍后才将结果移至r9temp2)。

sum += src1[temp1] * src2[temp2]mla完成,可以读取和写入r4sum)。

为什么您需要temp1temp2为全局?这只会阻止优化器进行积极的优化,而这些优化并不能完全计算与C源相同的临时值。幸运的是,C内存模型足够弱,它应该能够对它们进行重新排序,尽管这可能实际上是为什么它不直接MLA到temp2,因为它决定先进行计算。 (嗯,内存模型是否适用?其他线程根本看不到我们的寄存器,因此这些全局变量都是有效的线程本地。它应该允许放宽排序以分配全局变量。信号处理程序可以看到这些全局变量,并且可以任何一点都运行.gcc不遵循严格的源顺序,因为在源代码中,两个乘法都在添加之前发生。)

Godbolt没有更新的ARM gcc版本,因此我无法轻松测试更新的gcc。一个较新的gcc可能会做得更好。

BTW,I tried a version of the function using local variables for temporaries, and didn't actually get better results。可能是因为仍有这么多的寄存器全局,gcc无法为临时选择方便的注册。

// same register globals, except for temp1 and temp2.

void calc_local_tmp() {
  int t1 = r*n + k;
  sum += src1[t1] * src2[k*n + c];
}
    push    {lr}                      @ gcc decides to push to get a tmp reg
    movw    r3, #:lower16:.LANCHOR0   @ tmp131,
    mla     lr, r10, r5, r7           @ tmp133, n.1, r, k.2
    movt    r3, #:upper16:.LANCHOR0   @ tmp131,
    mla     ip, r7, r10, r6           @ tmp137, k.2, n.1, c
    ldr     r2, [r3]                  @ src1, src1
    ldr     r0, [r3, #4]              @ src2, src2
    ldr     r1, [r2, lr, lsl #2]      @ *_10, *_10
    ldr     r3, [r0, ip, lsl #2]      @ *_20, *_20
    mla     r4, r3, r1, r4            @ sum, *_20, *_10, sum
    ldr     pc, [sp], #4              @

使用-fcall-used-r8 -fcall-used-r9进行编译没有帮助; gcc使用相同的代码推送lr以获得额外的临时性。它无法使用ldmia(加载 - 倍数),因为它会对哪个临时放入哪个reg进行次优选择。 (&src1中的r0会将src1src2加载到r2r3。)