GCC为AVR上的简单ISR产生不必要的寄存器推送

GCC produces unneccessary register pushes for simple ISR on AVR

本文关键字:不必要 寄存器 ISR AVR 简单 GCC      更新时间:2023-10-16

我有一些简单的C++程序,如果用g++编译,它会产生以下汇编程序文本。唯一的语句是sbi,它不影响任何状态标志。我想知道为什么G++会产生r0r1的这些无用的推/弹出?

.global __vector_14
.type   __vector_14, @function
__vector_14:
push r1  ; 
push r0  ; 
in r0,__SREG__   ; ,
push r0  ; 
clr __zero_reg__         ; 
/* prologue: Signal */
/* frame size = 0 */
/* stack size = 3 */
.L__stack_usage = 3
sbi 0x1e,0       ; ,
/* epilogue start */
pop r0   ; 
out __SREG__,r0  ; ,
pop r0   ; 
pop r1   ; 
reti
.size   __vector_14, .-__vector_14

有没有办法让g++自动省略这些寄存器保存。我一般不想将ISR声明为ISR_NAKED

编辑:这是相应的C++代码(-Os或-O3):

#include <avr/interrupt.h>
struct AppFlags final {
bool expired : 1;
} __attribute__((packed));
int main() {
}
ISR(TIMER0_COMPA_vect) {
auto f = reinterpret_cast<volatile AppFlags*>(0x3e);
f->expired = true;
}

原因是您使用的是过时的编译器。上述优化已添加到v8(2018年春季发布)中,请参阅GCC v8发布说明:

编译器现在生成高效的中断服务例程(ISR)序言和尾声。这是通过使用GNU汇编程序支持和解决的新AVR伪指令__gcc_isr来实现的。

简单答案:

https://gcc.gnu.org/bugzilla/show_bug.cgi?id=20296

困难在于,目前avr后端的架构没有很容易允许改进这种情况:每个指令模式(如"multiply两个16位整数"或"符号将16位变量扩展到32位")可以自由地假设它可能覆盖或更改r0和r1,除非它离开">zero_reg"在完成任务后使用0。

要解决这个问题,IMHO需要对后端。

这是对avr后端的一个长期错误/增强请求。

GCC推送所有使用的寄存器。您唯一真正的办法是启用naked属性,它只会推送堆栈指针。或者更改为汇编语言。

GCC版本8之前没有优化ISR序言和尾声。GCC 8及更高版本现在在使用某些优化级别(如-Os)进行编译或提供-mgas-isr-prologues时,会发出包含ISR主体的__gcc_isr伪指令。

GNU汇编程序(来自不太过时的binutils版本)理解这些伪指令,并扫描__gcc_isr 1__gcc_isr 2之间的指令(通过),以决定r0(tmp寄存器)、r1(零寄存器)和SREG(状态寄存器)中的哪一个需要保存和恢复。

因此,对于您的示例,我得到了一个非常小的objdump(当使用GCC 11.1编译时):

$ avr-objdump -d foo.o
[..]
00000000 <__vector_14>:
0:   f0 9a           sbi 0x1e, 0 ; 30
2:   18 95           reti
[..]

当我告诉GCC只发出汇编时,我们看到伪指令:

$ avr-g++ -c -S -Os -mmcu=atmega328p  foo.c -fno-exceptions
$ cat foo.s
[..]
.global __vector_14
.type   __vector_14, @function
__vector_14:
__gcc_isr 1
/* prologue: Signal */
/* frame size = 0 */
/* stack size = 0...3 */
.L__stack_usage = 0 + __gcc_isr.n_pushed
sbi 0x1e,0
/* epilogue start */
__gcc_isr 2
reti
__gcc_isr 0,r0
[..]