gpt4 book ai didi

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

转载 作者:太空宇宙 更新时间:2023-11-04 02:36:25 25 4
gpt4 key购买 nike

这是一个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”?

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

最佳答案

我举了真实的例子(发表在评论中)和put it on the godbolt compiler explorer . calc() 的主要低效率是 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) 指令使用 r8 (temp1) 作为目标,但是另一个写入 r0(临时寄存器),然后才将结果移动到 r9(temp2)。

sum += src1[temp1] * src2[temp2] 是通过 mla 读取和写入 r4 (求和).

为什么需要 temp1temp2 是全局变量?这只会阻止优化器进行积极的优化,这些优化不会计算与 C 源代码完全相同的临时文件。幸运的是,C 内存模型足够弱,它应该能够重新排序分配给它们,尽管这实际上可能是它没有直接将 MLA 放入 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 做出了次优选择。 (&src1 in r0 将让它加载 src1src2r2r3.)

关于c - 为什么 gcc (ARM) 不使用全局寄存器变量作为源操作数?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/36635242/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com