Search code examples
cavr-gcc

avr-gcc woes: ignores permanent variable/register binding. Why?


Still struggling with AVR assembly. This time avr-gcc seems to completely ignore my directive to permanently bind a local variable to a register. Here's an example — this is of course just an illustration, not the final code:

// C code:
ISR(USART1_RX_vect)
{
    register uint8_t c asm("r3") = UDR1;
    tty1::buffer[tty1::ptr.head] = c;
}

// Generated assembly:
000000d8 <__vector_20>:
  d8:   1f 92           push    r1
  da:   0f 92           push    r0
  dc:   0f b6           in      r0, SREG        ; 63
  de:   0f 92           push    r0
  e0:   11 24           eor     r1, r1
  e2:   8f 93           push    r24
  e4:   ef 93           push    r30
  e6:   ff 93           push    r31
  e8:   80 91 73 00     lds     r24, UDR1       ; 0x800073 <__EEPROM_REGION_LENGTH__+0x7f0073>
  ec:   e0 91 01 01     lds     r30, 0x0101     ; 0x800101 <tty<drv::uart1>::ptr>
  f0:   e2 95           swap    r30
  f2:   ef 70           andi    r30, 0x0F       ; 15
  f4:   f0 e0           ldi     r31, 0x00       ; 0
  f6:   ee 5f           subi    r30, 0xFE       ; 254
  f8:   fe 4f           sbci    r31, 0xFE       ; 254
  fa:   80 83           st      Z, r24
  fc:   ff 91           pop     r31
  fe:   ef 91           pop     r30
 100:   8f 91           pop     r24
 102:   0f 90           pop     r0
 104:   0f be           out     SREG, r0        ; 63
 106:   0f 90           pop     r0
 108:   1f 90           pop     r1
 10a:   18 95           reti

- Give me r3, please.

- Sure thing! here's r24.

I can ask any register between r2 and r7 that compiler just takes whatever it wants! And it has nothing to do with UDR1, it does that with whatever I assign c. What's the point of that directive if it does nothing at all? How am I supposed to control the register the compiler selects?

To the question «Why the heck am I wanting to assign variable to a register?» I reply «Because the generated code is sub-optimal for an interrupt and I want a fine control over the generated assembly.» So far it's been a trouble for me.

Still using avr-gcc version 7.1.0...


Solution

  • Remove the declaration from the Interrupt Service Routine (ISR) to the global scope. GCC allows global and local permanent bindings. Yours is in the ISR scope, which is but useless in your case.

    I will try my best to give a more detailed explanation.

    Local scope

    1. Register from the binding stops to be a register, and it is treated as the storage location.
    2. The same optimisation rules apply as to any other automatic variables.
    3. The ISR has to restore all of the registers (except those bound in the global scope) - so no side effects possible - even if registers are bound in the local scope.

    See the code: https://godbolt.org/g/HsjvCa and https://godbolt.org/g/ZGjvRE with optimisations on.

    Local bindings do not change any general register use rules.

    If you bind registers - you can't call any functions compiled without header files with register bindings (global scope). In the local scope call to any function invalidates the local bindings.