GCC在ISR中生成无用的代码 [英] GCC generating useless code in ISR
问题描述
我有一个非常简单的中断服务例程(ISR),该例程是为atmega328编写的,并使用AVR Studio通过avrgcc(使用-Os)进行了编译.
I have a very simple Interrupt Service Routine(ISR) written for the atmega328 and compiled with avrgcc (using -Os) using AVR studio.
ISR (TIMER0_OVF_vect) {
txofcnt++; //count overflows and store in uint16_t
}
如果您注意到生成的程序集(如下所示),它将使用r24,r25来使volatile uint16_t txofcnt递增操作,但它还会压入-写入-弹出r1,r28,r29而不会读取它们.它还具有r0的额外推入/弹出操作,而无需在两者之间使用它.
If you note the assembly generated (below), it uses r24, r25 to get the job incrementing the volatile uint16_t txofcnt, but it also push-write-pop r1, r28, r29 without ever reading them. It also has an extra push/pop of r0 without ever using it in between.
我完全不明白为什么将r1推入,清除然后最终弹出. 但是,为什么gcc觉得有必要将EIMSK和GPIOR0加载到寄存器中,然后再不使用它们.奖励点是,如果您能告诉我GPIOR0的用途,则数据表上说它存在但没有描述.
I flat out don't understand why r1 is pushed, cleared and then finally poped. But also why does gcc feel the need to load EIMSK and GPIOR0 into registers and then not use them. Bonus points if you can tell me what GPIOR0 is even for, the datasheet says it exists but has no description.
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
推荐答案
在 https://gcc.gnu.org/wiki/avr-gcc
与您的问题有关的一些段落:
Some passages relevant to your question:
固定寄存器
固定寄存器是不会由GCC分配的寄存器 注册分配器.寄存器R0和R1固定并隐式使用 在打印汇编程序说明时:
Fixed Registers are registers that won't be allocated by GCC's register allocator. Registers R0 and R1 are fixed and used implicitly while printing out assembler instructions:
-
R0
R0
用作暂存器,使用后无需恢复.必须将其保存并恢复到中断服务程序的
(ISR)的序言和结尾.在内联汇编器中,您可以使用
__tmp_reg__
用于暂存寄存器.
is used as scratch register that need not to be restored after its usage. It must be saved and restored in interrupt service routine's
(ISR) prologue and epilogue. In inline assembler you can use
__tmp_reg__
for the scratch register.
R1
始终包含零.在insn期间,内容可能会被破坏,例如通过使用R0/R1作为隐式的MUL指令
输出寄存器.如果insn破坏了R1,则insn必须将R1恢复为
之后为零.该寄存器必须保存在ISR序言中,并且必须
然后将其设置为零,因为R1可能包含非零值.
ISR结语恢复了价值.在内联汇编器中,您可以使用
__zero_reg__
表示零寄存器.
always contains zero. During an insn the content might be destroyed, e.g. by a MUL instruction that uses R0/R1 as implicit
output register. If an insn destroys R1, the insn must restore R1 to
zero afterwards. This register must be saved in ISR prologues and must
then be set to zero because R1 might contain values other than zero.
The ISR epilogue restores the value. In inline assembler you can use
__zero_reg__
for the zero register.
...
通话记录
可用于呼叫或呼叫中断的通用寄存器(GPR)为 可能被函数调用破坏(破坏)的寄存器.
The call-used or call-clobbered general purpose registers (GPRs) are registers that might be destroyed (clobbered) by a function call.
-
R18–R27,R30,R31
R18–R27, R30, R31
这些GPR被称为破坏者".普通函数可以使用它们而不恢复内容.中断服务程序(ISR)必须 保存并恢复他们使用的每个寄存器.
These GPRs are call clobbered. An ordinary function may use them without restoring the contents. Interrupt service routines (ISRs) must save and restore each register they use.
...
呼叫保存的寄存器
-
R2-R17,R28,R29
R2–R17, R28, R29
其余的GPR被保存,即使用此类寄存器的函数必须恢复其原始内容.即使使用寄存器来传递函数参数也是如此.
The remaining GPRs are call-saved, i.e. a function that uses such a registers must restore its original content. This applies even if the register is used to pass a function argument.
以下是我对编译器为何在ISR序言/结尾中执行一些显然不必要的寄存器保存/恢复的猜测:
What follows is my speculation on why the compiler performs some apparently unnecessary register save/restores in the ISR prologue/epilogue:
-
r0
和r1
被保存/恢复,因为编译器生成或调用的代码将做出上面概述的假设.由于GCC的寄存器分配器未跟踪它们,因此序言必须确保将它们保存(并且在r1
的情况下初始化为0).
r0
andr1
are saved/restored because code that the compiler generates or calls will make the assumptions outlined about them above. Since they aren't tracked by GCC's register allocator, the prologue must make sure they're saved (and inr1
's case initialized to 0).
r28
和r29
用于保存堆栈指针(0x3d
/SPL
和0x3e
/SPH
).我正在猜测(并且我想强调的是,这是一个猜测),编译器作者假设中断处理程序交换堆栈可能很常见,并且这确保了ISR可以还原在使用时所使用的堆栈.发生中断.编译器可以假定这些寄存器不会被调用函数更改,因为它们是保存调用"的寄存器.
r28
and r29
are used to save the stack pointer (0x3d
/SPL
and 0x3e
/SPH
). I'm guessing (and I want to stress that this is a guess) that the compiler writers assume that it might be common for an interrupt handler to swap stacks, and this makes sure that the ISR can restore that stack that was in use when the interrupt occurred. The compiler can assume that these registers won't be altered by called functions since they are "call-saved" registers.
此外,您还应注意,显然是额外"推入& r0
的弹出窗口用于将SREG
状态寄存器保存在堆栈中.即使在这些push
和pop
指令之间未使用r0
,请记住r0
寄存器是暂存寄存器,寄存器分配器未对其进行跟踪,因此编译器不会假定<将SREG
加载到其中后,c2>不会更改.
Also, you should note that the apparently "extra" push & pop of r0
are to save the SREG
status register on the stack. Even though r0
isn't used between those push
and pop
instructions, remember that the r0
register is a scratch register that isn't tracked by the register allocator, so the compiler won't assume that r0
will not have changed after it loads SREG
into it.
作为补充,对0x3d
和0x3e
的读取是SPL
和SPH
堆栈指针寄存器,而不是EIMSK
和GPIOR0
寄存器.请参见
As a side note, the reads of 0x3d
and 0x3e
are the SPL
and SPH
stack pointer registers, not the EIMSK
and GPIOR0
registers. See Note 4 of the Register Summary table on page 625 in the reference manual here for detail on how the register addressing differs when using the IN
/OUT
instructions instead of a load or store instruction.
关于GPIOR0
的奖励积分:
8.5.1通用I/O寄存器
8.5.1 General Purpose I/O Registers
ATmega48A/PA/88A/PA/168A/PA/328/P包含三个通用 I/O寄存器.这些寄存器可用于存储任何 信息,它们对于存储全局信息特别有用 变量和状态标志.通用I/O寄存器 地址范围0x00-0x1F可以使用SBI直接进行位访问, CBI,SBIS和SBIC指令.
The ATmega48A/PA/88A/PA/168A/PA/328/P contains three General Purpose I/O Registers. These registers can be used for storing any information, and they are particularly useful for storing global variables and Status Flags. General Purpose I/O Registers within the address range 0x00 - 0x1F are directly bit-accessible using the SBI, CBI, SBIS, and SBIC instructions.
这篇关于GCC在ISR中生成无用的代码的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!