assembly 为什么Clang在进入一个程序时会在ARM ip寄存器中产生多个加载?

nzrxty8p  于 2023-04-30  发布在  其他
关注(0)|答案(1)|浏览(101)

我正在使用一些C代码来检查我的编译器(在本例中为main-git Clang)如何处理ARM ABI中堆栈的函数参数wrt。我发现这个函数:

int test(int a, int b, int c, int d, int e, int f, int g) {
  return a + b + c + d + e + f + g;
}

得到翻译,与-O0,在

08000298 <test>:
 8000298:   b084        sub sp, #16
 800029a:   f8dd c018   ldr.w   ip, [sp, #24]
 800029e:   f8dd c014   ldr.w   ip, [sp, #20]
 80002a2:   f8dd c010   ldr.w   ip, [sp, #16]
 80002a6:   9003        str r0, [sp, #12]
 80002a8:   9102        str r1, [sp, #8]
 80002aa:   9201        str r2, [sp, #4]
 80002ac:   9300        str r3, [sp, #0]
 80002ae:   9803        ldr r0, [sp, #12]
 80002b0:   9902        ldr r1, [sp, #8]
 80002b2:   4408        add r0, r1
 80002b4:   9901        ldr r1, [sp, #4]
 80002b6:   4408        add r0, r1
 80002b8:   9900        ldr r1, [sp, #0]
 80002ba:   4408        add r0, r1
 80002bc:   9904        ldr r1, [sp, #16]
 80002be:   4408        add r0, r1
 80002c0:   9905        ldr r1, [sp, #20]
 80002c2:   4408        add r0, r1
 80002c4:   9906        ldr r1, [sp, #24]
 80002c6:   4408        add r0, r1
 80002c8:   b004        add sp, #16
 80002ca:   4770        bx  lr

请注意ip中的值的初始存储。**为什么有?**它们对我来说毫无用处,即使是-O0。(-O0解释了why it spills a to d from r0-r3 to the stack函数入口,并在需要时重新加载它们。但是这些堆栈参数没有做任何事情。)
我不明白在这种情况下ip的用法是什么,因为它在后面没有被使用,也没有被用作帧指针(iiuc是它的正常用法吗?)。谢谢!

bybem2ql

bybem2ql1#

序言中堆栈参数的虚拟加载似乎只是clang的-O0代码生成器跨多个体系结构的一个怪癖。它已经存在很长一段时间了(Godbolt最早的叮当版本,3。0,表现出这种行为)。这完全没用,没有理由这是有帮助的,gcc -O0不这样做。
没有ABI或asm需要理解,这只是-O0代码生成中许多低效之一,这并不有趣。
(GCC -O0 * 会 * 为叶函数设置帧指针,不像clang)。
针对x86-64、i386、RISC-V、MIPS和AArch 64的Clang在所有这些设备上都做了同样的事情。(也是PowerPC,我想,如果我阅读得正确的话。)通常clang会为每个堆栈参数选择不同的寄存器,但Godbolt上的clang 16会加载4次到EAX中,覆盖它。我添加了更多的args,总共10个,因为x86-64 SysV在regs中传递最多6个,而一些像RISC-V和AARch 64,在寄存器中传递最多8个整数args。
另一个例子:https://godbolt.org/z/x4vzcYdEn-开始附近的mov eax, [rbp + 24]mov r10d, [rbp + 16]是使用不同寄存器的x86-64 GCC示例。
早期的ARMv7 clang并不是对每个负载都使用ip。它将它们传播到其他寄存器,如R12,LR,R4和R5(在推送这些寄存器之后,它可以覆盖它们。)有了更多的函数args,那些clang 11甚至push都有更多的寄存器,这样它们就可以加载函数序言中的所有堆栈args,即与打开的{相关联的asm块(在调试元数据中)。所以clang(trunk)实际上变得不那么糟糕了!
32位x86的旧clang也使用额外的寄存器,并将一些堆栈参数复制到它为局部变量保留的空间中。还是把它们都复制下来,有些放在序言里,有些放在寄存器里,直到结尾?clang 8.0.1 on Godbolt非常古怪,对于x86和-m32
我不知道编译器内部的什么细节导致了这种行为。或者甚至它是否存在于LLVM-IR中,或者仅在asm的最终生成期间。
我很好奇,如果运行一个优化通道来摆脱这些虚拟加载,而不是为它们发出机器代码(并在优化时将它们放在数据结构中),是否会加快整体编译时间。)
我想知道“触摸”所有的args是否与clang如何安排在序言中溢出寄存器args有关,这是为了实现100%一致的调试而需要的。

相关问题