golden bananas
golden bananas

Reputation: 41

Where is the offset of the Y register from the call/stack frame in avr-gcc coming from?

On the avr-gcc website (https://gcc.gnu.org/wiki/avr-gcc#Frame_Layout) it says that the frame pointer (Y register) is off by one byte so Y+1 points to the bottom of the frame. However when I compiled a test C function to an assembly file and looked at the prologue, it looks to me as if Y and SP are pointing to the same location and there is no offset from the frame.

But in the main body it is indeed looking like it using Y as if it is just off of the frame.
I am not sure where this offset is coming from. here is the assembly code the compiler gave me for the function with my questions marked on it:

func:
  push r29
  push r28 <- save Y as it is preserved
  rcall . <- this seems to be used to decrement the stack pointer to allocate stack space 2 bytes at a time
  rcall .
  rcall .

  in r28,__SP_L__
  in r29,__SP_H__ <- the stack pointer and Y should be aligned and both pointing to last local
/* prologue: function */

/* frame size = 6 */
  std Y+4,r24
  std Y+6,r23 <- should this not be overwriting the saved r28 register?
  std Y+5,r22
  ldi r24,lo8(97)
  std Y+3,r24
  ldi r24,lo8(23)
  ldi r25,hi8(23)
  std Y+2,r25
  std Y+1,r24 <- does seem to use Y as if it is one less than the stack frame. but after the decrements

  ldd r24,Y+4
  mov r18,r24
  clr r19
  sbrc r18,7
  com r19
  ldd r24,Y+5
  ldd r25,Y+6
  add r18,r24
  adc r19,r25
  ldd r24,Y+3
  clr r25
  sbrc r24,7
  com r25
  add r18,r24
  adc r19,r25
  ldd r24,Y+1
  ldd r25,Y+2
  add r24,r18
  adc r25,r19

/* epilogue start */
  adiw r28,6
  in __tmp_reg__,__SREG__
  cli
  out __SP_H__,r29
  out __SREG__,__tmp_reg__
  out __SP_L__,r28
  pop r28
  pop r29
  ret

I would expect there to be either an extra decrement of Y or for the main body to use Y .. Y+3 not Y+1 .. Y+4. The offset or decrement doesn't seem obvious too me. I have checked the instruction set manual but none of instructions seem to work in a way that makes this make sense assuming I understood what it was telling me.

I know this might be a stupid question, but I generally don't understand where this offset is coming from. I even asked Claude and it didn't understand either.

Upvotes: 4

Views: 51

Answers (1)

emacs drives me nuts
emacs drives me nuts

Reputation: 3993

it looks to me as if Y and SP are pointing to the same location

Yes. That's how a function prologue is setting up the frame pointer (provided there is need for a FP). Here is a small example:

void fn_frame (void)
{
    int volatile x; // Forces a frame
    x = 0;
}

The generates assembly with -S -Os and avr-gcc v8 reads:

fn_frame:
    push r28
    push r29
    rcall .
    in r28,__SP_L__
    in r29,__SP_H__
/* prologue: function */
/* frame size = 2 */
/* stack size = 4 */
.L__stack_usage = 4
    std Y+2,__zero_reg__
    std Y+1,__zero_reg__
/* epilogue start */
    pop __tmp_reg__
    pop __tmp_reg__
    pop r29
    pop r28
    ret

This means that x lives in Y+1...Y+2, and the bottom of the frame is Y+1, not Y.

This is a decision of how the frame is implemented in avr-gcc. Though I don't know the exact reason for that design decision, here is one point in favour of it:

  • The initialization of the frame pointer is just the two IN instructions that set FP = SP so the code can access into the stack frame. When FP would be set at the bottom of the frame at SP + 1, then setting up FP would require 3 instructions for that test case:

    in r28,__SP_L__
    in r29,__SP_H__
    adiw r28,1
    

    On the other hand, address Y+1 is as effcient as Y; the differenct only becomes relevant when the addressing mode is exhausted above Y+63.

Also notice that PUSH is a post-decrement instruction, i.e. SP points one below the most recently pushed value.

Upvotes: 3

Related Questions