GCC在ISR中生成无用的代码 [英] GCC generating useless code in ISR

查看:143
本文介绍了GCC在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:

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

  • r0 and r1 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 in r1's case initialized to 0).

r28r29用于保存堆栈指针(0x3d/SPL0x3e/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状态寄存器保存在堆栈中.即使在这些pushpop指令之间未使用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.

作为补充,对0x3d0x3e的读取是SPLSPH堆栈指针寄存器,而不是EIMSKGPIOR0寄存器.请参见

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屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆