Reputation: 41
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
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