【问题标题】:GCC generating useless code in ISRGCC 在 ISR 中生成无用代码
【发布时间】:2014-05-29 21:15:10
【问题描述】:

我为 atmega328 编写了一个非常简单的中断服务例程 (ISR),并使用 AVR studio 用 avrgcc(使用 -Os)编译。

ISR (TIMER0_OVF_vect) { 
    txofcnt++;  //count overflows and store in uint16_t 
}

如果您注意到生成的程序集(如下),它使用 r24、r25 来完成增加 volatile uint16_t txofcnt 的作业,但它也无需读取它们就 push-write-pop r1、r28、r29。它还有一个额外的 r0 推送/弹出,而无需在两者之间使用它。

我完全不明白为什么 r1 被推送、清除然后最终弹出。 但也是为什么 gcc 觉得需要将 EIMSK 和 GPIOR0 加载到寄存器中然后不使用它们。如果您能告诉我 GPIOR0 的用途,则可以加分,数据表说它存在但没有描述。

00000258 <__vector_16>:

ISR (TIMER0_OVF_vect) {
 258:   1f 92           push    r1
 25a:   0f 92           push    r0
 25c:   00 90 5f 00     lds r0, 0x005F
 260:   0f 92           push    r0
 262:   11 24           eor r1, r1
 264:   8f 93           push    r24
 266:   9f 93           push    r25
 268:   cf 93           push    r28
 26a:   df 93           push    r29
 26c:   cd b7           in  r28, 0x3d   ; 61 reads register EIMSK
 26e:   de b7           in  r29, 0x3e   ; 62 reads register GPIOR0
    txofcnt++;  
 270:   80 91 0a 01     lds r24, 0x010A
 274:   90 91 0b 01     lds r25, 0x010B
 278:   01 96           adiw    r24, 0x01   ; 1
 27a:   90 93 0b 01     sts 0x010B, r25
 27e:   80 93 0a 01     sts 0x010A, r24
}
 282:   df 91           pop r29
 284:   cf 91           pop r28
 286:   9f 91           pop r25
 288:   8f 91           pop r24
 28a:   0f 90           pop r0
 28c:   00 92 5f 00     sts 0x005F, r0
 290:   0f 90           pop r0
 292:   1f 90           pop r1
 294:   18 95           reti

【问题讨论】:

  • 您的目标是什么设备?在某些 AVR 上,寄存器 0x​​3d 和 0x3e 是堆栈指针寄存器。 ISR序言可能被“硬编码”以将堆栈指针存储在被调用者保存的寄存器r28r29中,以防ISR代码交换到中断堆栈?但我只是猜测。
  • 正如我上面所说的 MEGA328
  • 我很惊讶为什么 gcc 这样做(我知道,'固定/临时寄存器,等等等等')以及为什么,十多年来仍然没有任何好的答案,当人们想知道为什么时,他们只是被告知要“裸”或在汇编中编写 ISR。让 gcc 跟踪和消除这里的冗余代码真的有多难?

标签: c gcc interrupt avr


【解决方案1】:

https://gcc.gnu.org/wiki/avr-gcc 有一些关于 GCC 对 AVR 的寄存器使用的文档

与您的问题相关的一些段落:

固定寄存器

固定寄存器是不会被 GCC 分配的寄存器 寄存器分配器。寄存器 R0 和 R1 是固定的并隐式使用 在打印出汇编指令时:

  • R0

    用作暂存器,使用后无需恢复。它必须在中断服务程序中保存和恢复 (ISR) 序幕和尾声。在内联汇编器中,您可以使用 __tmp_reg__ 用于暂存寄存器。

  • R1

    总是包含零。在insn期间,内容可能会被破坏,例如通过使用 R0/R1 作为隐式的 MUL 指令 输出寄存器。如果 insn 破坏了 R1,则 insn 必须将 R1 恢复为 之后归零。该寄存器必须保存在 ISR 序言中,并且必须 然后设置为零,因为 R1 可能包含零以外的值。 ISR 结语恢复了价值。在内联汇编器中,您可以使用 __zero_reg__ 用于零寄存器。

    ...

调用使用的寄存器

调用使用或调用破坏的通用寄存器 (GPR) 是 可能被函数调用破坏(破坏)的寄存器。

  • R18–R27、R30、R31

    这些 GPR 被调用破坏。普通功能可以使用它们而不恢复内容。中断服务程序 (ISR) 必须 保存并恢复他们使用的每个寄存器。

    ...

通话保存的寄存器

  • R2–R17、R28、R29

    剩余的 GPR 是调用保存的,即使用此类寄存器的函数必须恢复其原始内容。即使寄存器用于传递函数参数也是如此。

以下是我对编译器为何在 ISR 序言/尾声中执行一些明显不必要的寄存器保存/恢复的推测:

  • r0r1 被保存/恢复,因为编译器生成或调用的代码将做出上述关于它们的假设。由于 GCC 的寄存器分配器不会跟踪它们,因此序言必须确保它们被保存(并且在 r1 的情况下初始化为 0)。

  • r28r29用于保存堆栈指针(0x3d/SPL0x3e/SPH)。我在猜测(我想强调这是一个猜测)编译器编写者假设中断处理程序交换堆栈可能很常见,这确保 ISR 可以恢复在发生中断。编译器可以假设这些寄存器不会被调用函数更改,因为它们是“调用保存”的寄存器。

另外,您应该注意r0 的明显“额外”推送和弹出是为了将SREG 状态寄存器保存在堆栈中。即使r0 没有在pushpop 指令之间使用,请记住r0 寄存器是寄存器分配器不跟踪的临时寄存器,因此编译器不会假定@ 987654340@ 在将SREG 加载到其中后不会发生变化。

附带说明,0x3d0x3e 的读取是 SPLSPH 堆栈指针寄存器,而不是 EIMSKGPIOR0 寄存器。有关使用IN/OUT 指令而不是加载或存储指令时寄存器寻址有何不同的详细信息,请参见reference manual here 第 625 页上的寄存器摘要表的注 4。

关于GPIOR0的奖励积分:

8.5.1 通用 I/O 寄存器

ATmega48A/PA/88A/PA/168A/PA/328/P 包含三个通用 I/O 寄存器。这些寄存器可用于存储任何 信息,它们对于存储全局信息特别有用 变量和状态标志。通用 I/O 寄存器内 地址范围 0x00 - 0x1F 可以使用 SBI 直接进行位访问, CBI、SBIS 和 SBIC 指令。

【讨论】:

    【解决方案2】:

    请记住,ISR 中断当前在 CPU 上执行的任何代码。他们必须小心在进入时(序言)保存当前的 CPU 状态,并在退出时恢复(尾声),以便被中断的代码可以正常继续。

    在 AVR 上,有几个寄存器不需要在正常功能的序言/尾声中保存/恢复,但需要由 ISR 保存:

    • r0 - 临时寄存器。普通函数可以使用它为所欲为,但 ISR 必须保留它。
    • r1 - 一个零寄存器,但可以通过普通函数中的指令写入。同样,必须由 ISR 保存。

    这解释了为什么 r0 和 r1 被保存。 GPIOR0 显然只是一个函数可能正在使用的另一个通用寄存器,因此也需要保存它。如果优化器可以判断出某些寄存器实际上没有被 ISR 修改,则其中一些可能会被优化,但优化器可能不是那么聪明,或者您可能需要尝试更高的优化级别。

    更多信息在http://gcc.gnu.org/wiki/avr-gcc

    【讨论】:

    • 但是为什么要加载 EIMSK 和 GPIOR0,加载后它们并没有被恢复、推送或读取。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-03-07
    • 1970-01-01
    • 1970-01-01
    • 2022-01-15
    • 2014-04-12
    • 1970-01-01
    相关资源
    最近更新 更多