#12447 - poslundc - Fri Nov 14, 2003 5:53 am
According to the Arm-Thumb Procedure Call Standard, if you have a variable number or more than four arguments in your function, every argument after the fourth will be stored in reverse order on the stack.
My question is: should my function move the stack pointer as it unloads the arguments off of the stack, or should I leave it where it was when the function began (in which case the procedure-calling routine presumably cleans up the parameters it set up for me)?
My gut says it's the former (it's a stack, after all), but if I'm wrong I will be generating a world of errors.
Thanks,
Dan.
#12452 - torne - Fri Nov 14, 2003 3:23 pm
Don't move the stack pointer. Read the local variables like this:
Code: |
ldr r5, [SP]
ldr r6, [SP, #4]
ldr r7, [SP, #8] |
and so on. The stack pointer points directly at the fifth argument and subsequent arguments are 4/8/12/16..etc bytes above the stack pointer.
Thumb has a special mode for writing SP-relative loads, so don't worry about Thumb not being able to use the required addressing mode.[/code]
#12462 - poslundc - Sat Nov 15, 2003 2:42 am
This presents me with a bit of a conundrum.
In my case, where I am attempting to write my own bastardized version of sprintf, I won't know the number of parameters I am receiving until I am done processing the string.
If the stack pointer isn't pointing to the top of the stack, but instead pointing to the beginning of my parameters, then I can't possibly know where the stack actually begins. Which means I can't get to the stack until I'm done loading in my parameters.
The thing is, I can't even do that, since all of my scratch registers are occupied with the first four parameters passed to my function. How can I save the remaining registers to the stack when I don't know where the stack begins?
It seems to me there must be a simple solution to this I cannot see; otherwise it would be impossible to write variadic functions.
Thanks,
Dan.
#12463 - tepples - Sat Nov 15, 2003 3:05 am
poslundc wrote: |
If the stack pointer isn't pointing to the top of the stack, but instead pointing to the beginning of my parameters, then I can't possibly know where the stack actually begins. Which means I can't get to the stack until I'm done loading in my parameters.
The thing is, I can't even do that, since all of my scratch registers are occupied with the first four parameters passed to my function. How can I save the remaining registers to the stack when I don't know where the stack begins?
|
Try implementing vsprintf(), and then set up a C wrapper that calls it. Then disassemble the C wrapper to see how the pros handle it.
Quote: |
It seems to me there must be a simple solution to this I cannot see; otherwise it would be impossible to write variadic functions. |
Try writing a variadic function, compiling it to assembly language, and reading it (gcc -mthumb -O -S foo.c -o foo.s; notepad foo.s).
_________________
-- Where is he?
-- Who?
-- You know, the human.
-- I think he moved to Tilwick.
#12467 - poslundc - Sat Nov 15, 2003 6:21 am
tepples wrote: |
Try writing a variadic function, compiling it to assembly language, and reading it (gcc -mthumb -O -S foo.c -o foo.s; notepad foo.s). |
That's my backup plan... but if someone already knows and can save me the trouble, my makefile is a bitch to get working on a single ASM file (part of running multiple versions of gcc on the same computer). :)
Dan.
#12470 - torne - Sat Nov 15, 2003 10:56 am
poslundc wrote: |
This presents me with a bit of a conundrum.
In my case, where I am attempting to write my own bastardized version of sprintf, I won't know the number of parameters I am receiving until I am done processing the string. |
That's no problem; you can use a loop. =)
poslundc wrote: |
If the stack pointer isn't pointing to the top of the stack, but instead pointing to the beginning of my parameters, then I can't possibly know where the stack actually begins. Which means I can't get to the stack until I'm done loading in my parameters. |
The stack pointer is pointing to the top of the stack. The GBA uses a full-descending stack - it grows downwards and the stack pointer always points to the last used entry, not the first empty one. Here's what the GBA stack looks like:
Code: |
^ +------------------------------+
| | Caller's stuff on stack | <- some high address
| +------------------------------+
Caller | Your nth argument | <- SP-(4*(n-5))
pushes +------------------------------+
and | Your n-1th argument | <- SP-(4*(n-6))
pops +------------------------------+
these | etc... |
| +------------------------------+
| | Your 5th argument | <- SP
v +------------------------------+
^ | First value you push |
| +------------------------------+
You do | Second value you push |
these +------------------------------+
| | etc... | <- new SP after you're done pushing
v +------------------------------+
|
poslundc wrote: |
The thing is, I can't even do that, since all of my scratch registers are occupied with the first four parameters passed to my function. How can I save the remaining registers to the stack when I don't know where the stack begins? |
You can. You can still get to your arguments after you push further things to the stack.
Say you push three registers:
This does SP = SP - 12 (4 bytes per register).
So, now you can get your fifth argument from SP+12, and your sixth argument from SP+16, and so on. =)
You just need to keep track of how many registers you push (in your head, not in code) and use that as the offset to find your parameters.
If you're implementing a variadic function, you might want to do something like this (using partial unrolling to deal with the first few params):
Code: |
@ Code for a variadic function with 2 fixed arguments that
@ implements a not-really-sprintf: it copies the string
@ pointed to by r1 to the location r0, replacing each %
@ in the string with the char pointed to by the next variadic argument.
@ I've not assembled this so expect small errors, but the arg handling
@ should be sound. It's partially unrolled to handle the first two variadic
@ args easily but I don't pretend it's optimised =)
notsprintf:
push {r4,r5} @ Save r4, r5 so we can change them
@ First loop: copy chars until we hit a null or a %, and
@ if it's a %, substitute char.
l1: ldrb r4, [r0] @ Load char
add r0, #1 @ increment source pointer
cmp r4, #0 @ check if null
beq end @ exit if null
cmp r4, #'% @ check if %
bne s1 @ skip ahead if not
ldrb r4, [r2] @ load char pointed to by first varg instead
s1: strb r4, [r1] @ store char
add r1, #1 @ increment target pointer
bne l1 @ Go back if we didn't do a substitution
@ Second loop: do the same but use next register for substitution
l2: ldrb r4, [r0] @ Load char
add r0, #1 @ increment source pointer
cmp r4, #0 @ check if null
beq end @ exit if null
cmp r4, #'% @ check if %
bne s2 @ skip ahead if not
ldrb r4, [r3] @ load char pointed to by second varg instead
s2: strb r4, [r1] @ store char
add r1, #1 @ increment target pointer
bne l2 @ Go back if we didn't do a substitution
@ Third to nth loop: do the same but read subs from stack
mov r5, sp @ Stick SP value into r5
add r5, #8 @ add 8 so it points to third varg (skipping our pushes)
loop: ldrb r4, [r0] @ Load char
add r0, #1 @ Increment source pointer
cmp r4, #0 @ check if null
beq end @ exit if null
cmp r4, #'% @ check if %
bne skip @ skip ahead if not
ldrb r4, [r5] @ Load pointer in nth varg
ldrb r4, [r4] @ Pointer deref
add r5, #4 @ increment vararg
skip: strb r4, [r1] @ store char
add r1, #1 @ increment target pointer
b loop @ Loop
@ Cleanup and return
end: strb r4, [r1] @ Append null to target
pop {r4,r5} @ pop stored vals (we didn't change SP so this is fine)
bx lr @ Return |
Hope that makes sense. Once you have the stack layout clear in your head it's pretty easy =)
#12473 - poslundc - Sat Nov 15, 2003 4:03 pm
OKAY, now I see. :)
It's the stupid stack terminology/behaviour that has me confused. From what you were telling me, I thought that the additional arguments were pushed onto the stack but that the stack pointer wasn't moved with them, which would create the problems I was describing.
I take it, then, that the calling routine takes responsibility for popping off the extra parameters once your routine is finished? This is what I was initially unsure about.
Thanks for the indepth explanation and sample code, by the way. I think all I really needed was a smack in the face and the message "the stack grows downward".
Dan.
#12523 - torne - Sun Nov 16, 2003 8:04 pm
poslundc wrote: |
I take it, then, that the calling routine takes responsibility for popping off the extra parameters once your routine is finished? This is what I was initially unsure about. |
Yes.
#12561 - poslundc - Mon Nov 17, 2003 10:28 pm
Thanks for your help, guys, especially torne. I've finished the function and tested it pretty thoroughly. I'll post it on gbadev as soon as I figure out how.
Dan.