【问题标题】:Ineffective stack management and registers allocation无效的堆栈管理和寄存器分配
【发布时间】: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

我对执行流程的理解如下:

  1. 将 R3-R7 + LR 推入堆栈(完全不清楚)
  2. 将 R0 移到 R5(这很清楚)
  3. 将 R1 移至 R6,将 R2 移至 R7(完全不清楚)
  4. 将 R5 取消引用到 R0(这很清楚)
  5. 比较 R0 和 0(这很清楚)
  6. 如果 R0 != 0 转到第 14 行: - 从 R6 恢复 R1,从 R7 恢复 R2 并调用 foo(), 如果 R0 == 0 停留在第 10 行,则从堆栈中恢复 R3 - R7 + PC(完全不清楚)
  7. 增加 R5(清除)
  8. 从 foo() 累积结果(清除)
  9. 分支回到 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 }

问题:

  1. 为什么 GCC 会为这种微不足道的功能存储这么多寄存器?

  2. 为什么在 ABI 中写入 R0-R3 是参数寄存器时它会推送 R3 并且应该是调用者保存并且应该在被调用函数中安全使用 在这种情况下 test()

  3. 为什么它复制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


【解决方案1】:
  1. 必须保存 LR,因为 test 不是叶函数。 r5-r7 被函数用来存储跨函数调用使用的值,因为它们不是临时的,所以必须保存它们。 r3 被推送以对齐堆栈。

  2. push 添加一个额外的寄存器是一种快速且紧凑的堆栈对齐方式。

  3. r1r2 可能会因调用 foo 而被丢弃,并且由于在调用后需要最初存储在这些寄存器中的值,因此它们必须存储在可以在调用中幸存的位置中。

【讨论】:

  • LR 存储和 8 字节堆栈对齐是显而易见的,超出了问题范围。你的第 3 点解释了我的疑虑。显然我错误地认为,如果我通过 R0-R3 传递指针,它们不应该在内部进行更改。
  • 你有三个传递引用变量需要在嵌套调用之外保留,你还有一个局部变量需要保留,所以那里有四个堆栈项,然后是返回地址和这个大小写对齐(如果你少一个变量,我敢打赌 gcc 会因为缺乏优化而烧掉两个堆栈位置,然后你会有一个有趣的问题)。所以它把这么多东西放在堆栈上的原因是你要求它有这么多变量。
  • arm gcc 后端更喜欢将寄存器保存在堆栈中并在函数中使用它们,而不是将变量保存在堆栈中并从堆栈中引用它们(ARM 不是 CISC,所以这是有道理的) .
猜你喜欢
  • 2010-11-15
  • 1970-01-01
  • 1970-01-01
  • 2016-06-26
  • 2023-03-23
  • 1970-01-01
  • 2017-04-28
  • 2011-10-09
  • 2020-10-10
相关资源
最近更新 更多