【发布时间】:2017-06-21 08:08:43
【问题描述】:
考虑以下代码:
extern unsigned int foo(char c, char **p, unsigned int *n);
unsigned int test(const char *s, char **p, unsigned int *n)
{
unsigned int done = 0;
while (*s)
done += foo(*s++, p, n);
return done;
}
汇编输出:
00000000 <test>:
0: b5f8 push {r3, r4, r5, r6, r7, lr}
2: 0005 movs r5, r0
4: 000e movs r6, r1
6: 0017 movs r7, r2
8: 2400 movs r4, #0
a: 7828 ldrb r0, [r5, #0]
c: 2800 cmp r0, #0
e: d101 bne.n 14 <test+0x14>
10: 0020 movs r0, r4
12: bdf8 pop {r3, r4, r5, r6, r7, pc}
14: 003a movs r2, r7
16: 0031 movs r1, r6
18: f7ff fffe bl 0 <foo>
1c: 3501 adds r5, #1
1e: 1824 adds r4, r4, r0
20: e7f3 b.n a <test+0xa>
使用 arm-none-eabi-gcc 版本编译的 C 代码:4.9.1、5.4.0、6.3.0 和 7.1.0 Linux 主机。所有 GCC 版本的程序集输出都是相同的。
CFLAGS := -Os -march=armv6-m -mcpu=cortex-m0plus -mthumb
我对执行流程的理解如下:
- 将 R3-R7 + LR 推入堆栈(完全不清楚)
- 将 R0 移到 R5(这很清楚)
- 将 R1 移至 R6,将 R2 移至 R7(完全不清楚)
- 将 R5 取消引用到 R0(这很清楚)
- 比较 R0 和 0(这很清楚)
- 如果 R0 != 0 转到第 14 行: - 从 R6 恢复 R1,从 R7 恢复 R2 并调用 foo(), 如果 R0 == 0 停留在第 10 行,则从堆栈中恢复 R3 - R7 + PC(完全不清楚)
- 增加 R5(清除)
- 从 foo() 累积结果(清除)
- 分支回到 a 行:(清除)
我自己的大会。没有经过广泛测试,但绝对不需要超过 R4 + LR 即可被推入堆栈:
编辑:根据提供的答案,我下面的示例将失败,因为 R1 和 R2 通过调用 foo() 不持久
51 unsigned int __attribute__((naked)) test_asm(const char *s, char **p, unsigned int *n)
52 {
53 // r0 - *s (move ptr to r3 and dereference it to r0)
54 // r1 - **p
55 // r2 - *n
56 asm volatile(
57 " push {r4, lr} \n\t"
58 " movs r4, #0 \n\t"
59 " movs r3, r0 \n\t"
60 "1: \n\t"
61 " ldrb r0, [r3, #0] \n\t"
62 " cmp r0, #0 \n\t"
63 " beq 2f \n\t"
64 " bl foo \n\t"
65 " add r4, r4, r0 \n\t"
66 " add r3, #1 \n\t"
67 " b 1b \n\t"
68 "2: \n\t"
69 " movs r0, r4 \n\t"
70 " pop {r4, pc} \n\t"
71 );
72 }
问题:
为什么 GCC 会为这种微不足道的功能存储这么多寄存器?
为什么在 ABI 中写入 R0-R3 是参数寄存器时它会推送 R3 并且应该是调用者保存并且应该在被调用函数中安全使用 在这种情况下 test()
为什么它复制R1到R6和R2到R7而extern函数的原型几乎 理想地匹配 test() 函数。所以 R1 和 R2 已经准备好通过了 到 foo() 例程。我的理解是,之前只需要取消引用 R0 调用 foo()
【问题讨论】:
-
R0-R3 没有被调用者保存,这意味着对
foo/fputc的调用可以修改这些寄存器。由于 R1 和 R2 可以修改,该函数将它们包含在 R6 和 R7 中的参数保存在被调用者保存的位置。 R4、R5、R6、R7都被函数修改了,所以需要保存,因为这些寄存器都是被调用者保存的。 R3不需要保存,所以我不知道为什么它保存在堆栈上。可能是出于对齐的原因。 -
对不起,不是被调用者保存,而是调用者保存在这种情况下,函数位于 test() 之一之上。我仍然看不出复制 R1 和 R2 的理由,foo() 在示例中应该是透明的。 foo() 的结果 - argument1 和 argument2 应该被视为 test() 的返回参数,因为它是顶级调用者。特别是如果您通过程序集监听 R1 和 R2 甚至在进入 foo() 之前恢复。还是我错了,是吗?
-
正如我所说,
foo可以修改 R1 和 R2。函数foo不需要在调用过程中保留这些寄存器,test也不需要。第二次和随后的时间foo被称为作为参数传递给test的值不能保证仍然在 R1 和 R2 中。即使foo实际上并没有改变 R1 和 R2,编译器也无法知道这一点,因此必须假设它会。 -
感谢您的 cmets,我刚刚发布了我的答案,这也适用于您在 @Johan 的答案下的最后评论。
标签: gcc assembly optimization cortex-m thumb