GCC generating useless code in ISR

avr, c, gcc, interrupt

Solution

There is some documentation on GCC's register usage for AVR at https://gcc.gnu.org/wiki/avr-gcc

Some passages relevant to your question:

Fixed Registers

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

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

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.

...

Call-Used Registers

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

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.

...

Call-Saved Registers

R2–R17, R28, R29

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.

What follows is my speculation on why the compiler performs some apparently unnecessary register save/restores in the ISR prologue/epilogue:

`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).

`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.

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.

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.

And for the bonus points regarding `GPIOR0`:

8.5.1 General Purpose I/O Registers

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.

Problem

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 } ``` 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. 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 ```

Original source