2016-04-14 35 views
1

这里是一个C源代码示例:为什么gcc(ARM)不使用全局寄存器变量作为源操作数?

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

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

而这是使用一个臂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寄存器,行为是相同的。 (更新,之前我已经说过-O3)

所以问题是:为什么海湾合作委员会增加第三和第四MOV而不是'ADD R3,R8,R9'?

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

+0

奇怪GCC决定不直接返回5。 – user3528438

+2

@ user3528438:对我来说'movs r0,#5; bx lr'。 – EOF

+0

关于-O3的观察与另一个代码一起使用,导致额外的MOV。使用此代码,它直接返回5。 – gma

回答

2

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

我没有看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(划痕REG),并且只是后来移动结果到r9temp2)。

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

为什么你需要temp1temp2为全局变量?这只会阻止优化器进行积极的优化,而这些优化不会计算出与C源完全相同的临时对象。幸运的是,C存储器模型足够弱,它应该能够重新排列它们的赋值,但这可能实际上是它直接将它转换为temp2的原因,因为它决定首先进行这种计算。 (嗯,内存模型是否适用?其他线程根本看不到我们的寄存器,所以这些全局变量都是线程本地的,它应该允许对全局变量进行宽松的排序,信号处理程序可以看到这些全局变量, gcc并没有遵循严格的源代码顺序,因为源码中的两个乘法都是在添加之前发生的。)

Godbolt没有更新的ARM gcc版本,所以我不能轻易测试更新的GCC。一个新的gcc可能会在这方面做得更好。


顺便说一句,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中。(&src1r0会让它加载src1src2r2r3

+0

谢谢彼得!主要目标是优化模拟inorder cpu(gem5 arm minorcpu)中的代码,该代码不重命名寄存器。这个CPU有一个有限的组织(例如只有一个乘法器),因此尝试增加了这个组织,并且放置了两个独立的指令块(但是没有重命名寄存器),我需要强制注册独立性。这里发布的代码是用来澄清问题的,原本没有calc函数的变量是局部的。 – gma

+0

@GermanoAndersson:你真的用'-O3 -frename-registers'获得次优代码吗? gcc手册页说这个选项专门针对没有硬件重命名的CPU进行优化。也许还可以使用'-mcpu = something_similar'来为现有的顺序CPU调整gcc,而无需重命名。 –

+0

-O3 gcc将MOVs + MUL更改为MLA,因此,对轻微cpu进行组织更改的优化不起作用。我需要两条不依赖的指令块,在向组织添加乘数时导致并行运行。 – gma

相关问题