最新消息:Welcome to the puzzle paradise for programmers! Here, a well-designed puzzle awaits you. From code logic puzzles to algorithmic challenges, each level is closely centered on the programmer's expertise and skills. Whether you're a novice programmer or an experienced tech guru, you'll find your own challenges on this site. In the process of solving puzzles, you can not only exercise your thinking skills, but also deepen your understanding and application of programming knowledge. Come to start this puzzle journey full of wisdom and challenges, with many programmers to compete with each other and show your programming wisdom! Translated with DeepL.com (free version)

assembly - Where is the offset of the Y register from the callstack frame in avr-gcc coming from? - Stack Overflow

matteradmin9PV0评论

On the avr-gcc website () 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.

On the avr-gcc website (https://gcc.gnu./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.

Share Improve this question edited Nov 18, 2024 at 8:40 emacs drives me nuts 4,03718 silver badges40 bronze badges asked Nov 16, 2024 at 23:40 golden bananasgolden bananas 411 silver badge2 bronze badges 2
  • (apparently I can't reply on the staging-ground after approving it there): Interesting, that'd do it. I approved this question from the staging ground you can post that [push stores and then decrements] as a self-answer. (With a link to push an online asm reference: content.alexandria.atmel/webhelp/… ) – Peter Cordes Commented Nov 16, 2024 at 23:42
  • For the matter of discussion it would help havong the C source available. – emacs drives me nuts Commented Nov 18, 2024 at 8:35
Add a comment  | 

1 Answer 1

Reset to default 3

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.

Post a comment

comment list (0)

  1. No comments so far