gbadev.org forum archive

This is a read-only mirror of the content originally found on forum.gbadev.org (now offline), salvaged from Wayback machine copies. A new forum can be found here.

ASM > Passing parameters to assembly functions

#15779 - gbawiz - Thu Jan 29, 2004 11:42 pm

Hello,
I am attempting to write some of my function procedures in assembly but my main code is in 'C'
How do I send parameters such as 'int variables' to my assembly function?

Thanx

#15781 - DekuTree64 - Fri Jan 30, 2004 12:10 am

This is a pretty common question around here, but here goes. The first 4 parameters are passed in r0-r3, the rest are pushed onto the stack. Check http://forum.gbadev.org/viewtopic.php?t=2338 for more info on how to retrieve the stack ones.
_________________
___________
The best optimization is to do nothing at all.
Therefore a fully optimized program doesn't exist.
-Deku

#15782 - poslundc - Fri Jan 30, 2004 12:19 am

The Arm-Thumb Procedure Call Standard is the definitive reference you need.

Generally, though, the first four parameters to a function are passed in registers r0-r3.

You can access global variables by specifying them in local code and then loading them into registers from there.

Edit: DekuTree64 beat me to it... :P

Dan.

#15783 - gbawiz - Fri Jan 30, 2004 1:09 am

poslundc wrote:

Generally, though, the first four parameters to a function are passed in registers r0-r3.
You can access global variables by specifying them in local code and then loading them into registers from there.
Dan.


Thanks for the advice.
How do you load variables into the registers in your 'C' source code for use in the routines which are written in assembly?

I tried defining the variables outside of the 'main' routine so that they are visible in all functions.
My 'c' source and assembly link and compile ok and i can reference the variables from the assembly but they seem to have the wrong values.
here is the examples:

-------------- 'C' source (main.c) -------------------
#include"gba.h"

extern void setpixel();

u16 pixel = 0x00FF;

int main()
{

REG_DISPCNT = 0x403;
setpixel();
while(1){}
}
------------------------------------------------

------------- assembly source (test.s)-----------------

@----- CODE START ----@
.global setpixel
setpixel:
ldr r1,=0x6000200
ldr r2, = 0x00ff
str r2, [r1]
bx lr @return;
@------ CODE END ----@

------------------------------------------------
and here is the command lines I use (inside a batch file)

set path=C:\devkitadv\bin;%path%
gcc test.s main.c
objcopy -O binary a.out test.gba
pause

#15784 - poslundc - Fri Jan 30, 2004 2:52 am

gbawiz wrote:
Thanks for the advice.
How do you load variables into the registers in your 'C' source code for use in the routines which are written in assembly?


An example would be as follows:

Code:
   ldr   r0, .L_GLOBALS   @ r0 <- address of myCVariable
   ldr   r1, [r0]      @ r1 <- contents of myCVariable
   str   r2, [r0]      @ myCVariable <- contents of r2
   b   SOMEWHERE_ELSE
.L_GLOBALS:
   .word   myCVariable


... where myCVariable is (as the name implies) a global variable in C.

Quote:
I tried defining the variables outside of the 'main' routine so that they are visible in all functions.
My 'c' source and assembly link and compile ok and i can reference the variables from the assembly but they seem to have the wrong values.
here is the examples:


OK, a few things.

1. Your example doesn't actually attempt to access any C variables, so it's impossible to tell what could be going wrong with it.

2. What exactly goes wrong with your code? ie. if it compiles and links okay, what do you expect it to do and what is it doing instead? Be specific.

3. Please post your code inside the code tags, as it makes it much easier to read.

Nothing jumps out as being "wrong" in the code you posted, other than the colour of the pixel you are plotting may not be what you are intending (0x00FF will give you 100% intensity red mixed with 50% intensity green).

I also think you're supposed to put a .pool directive at the end of your code if you're generating constants using the "ldr =" technique, although if it's compiling then it's probably just replacing the ldrs with mov and orr instructions.

You might also want to add the following tags to the beginning of your code:

Code:
   .section    .iwram   @ put your arm code in IWRAM, or use .text for ROM-based code
   .arm         @ specify arm instructions (vs. .thumb)
   .align   2   @ align to 4 bytes; not usually a problem but just in case
   .type   setpixel, function   @ useful for the linker, I suppose


Your stuff is compiling so none of the above stuff is absolutely necessary, but these are useful directives nevertheless.

Dan.

#15790 - torne - Fri Jan 30, 2004 12:01 pm

poslundc wrote:
I also think you're supposed to put a .pool directive at the end of your code if you're generating constants using the "ldr =" technique, although if it's compiling then it's probably just replacing the ldrs with mov and orr instructions.


You don't need to put .pool in most circumstances; the pool is automatically dumped at a section change (i.e. end of the file). You only need to use a .pool directive when the default location for the pool is too far away from one of the instructions that references it. Also, the ldr pseudoinstruction can only generate mov instructions (or ldr from a pool), it's not smart enough to do shifts/adds for you (and wouldn't know whether to or not, as it doesn't know the memory timing parameters of your target hardware).

#15797 - poslundc - Fri Jan 30, 2004 2:42 pm

Yeah, that makes sense. One of the reasons I never used the "ldr =" technique was because I was always sceptical about how it weighed the timing of memory accesses versus the timing of orr-ing together a constant. It makes far more sense that it just chooses between either mov-ing it in or ldr-ing it from the pool.

Dan.

#15800 - gbawiz - Fri Jan 30, 2004 4:43 pm

Hello,
Ive just had a look at the entry i made above,
the line which states: ldr r2, = 0x00ff should be ldr r2, = pixel
if you compile the 'test.s' file on its own in a GBA assembler then it should show a red dot on the screen which is what it should be according to the tutorial I used.
What i want to do is to pass a pixel value from the main 'C' code to the assembler function 'drawpixel'
Here is my attempt:

Firstly here is the c code within main.c
Code:

#include"gba.h"

u16 pixel = 0x00FF;
extern void setpixel();

int main()
{   
   REG_DISPCNT = 0x403;
   setpixel();
   while(1){}
}

//----------- end  --------------------


Now here is the assembly code within 'test.s'
Code:

#include"main.c"


@----- CODE START ----@
.L_GLOBALS:
   .word pixel

   .global setpixel
setpixel:
   ldr r4,=0x06000200
   ldr r5, = pixel
   str r5, [r4]
   bx lr @return;

@------ CODE END ----@



here is the batch file i use to create the rom


set path=C:\devkitadv\bin;%path%
gcc test.s main.c
objcopy -O binary a.out test.gba
pause



At some point the 'pixel' variable doesnt keep the 0x00ff value and the colour on the screen ends up wrong.

#15801 - poslundc - Fri Jan 30, 2004 4:54 pm

You need to read my example again. I'll give you a hint: you created a label called .L_GLOBALS but you aren't using it anywhere in your code. Suspicious, no?

Dan.

#15829 - torne - Sat Jan 31, 2004 4:15 pm

poslundc wrote:
Yeah, that makes sense. One of the reasons I never used the "ldr =" technique was because I was always sceptical about how it weighed the timing of memory accesses versus the timing of orr-ing together a constant. It makes far more sense that it just chooses between either mov-ing it in or ldr-ing it from the pool.


If you'd like a version that can compare memory timings, I wrote a macro which does this: http://forum.gbadev.org/viewtopic.php?t=154
It is set up under the assumption that the constant pool will be in ROM with default wait state timings, and will either generate shift/add sequences or a ldr = instruction.

#15830 - poslundc - Sat Jan 31, 2004 4:22 pm

Neat. I'll probably continue just to decide manually whether to load or generate my constants, however. :) Seeing as I'm constantly flip-flopping between putting my code in IWRAM and EWRAM and ROM anyway.

Dan.

#15840 - gbawiz - Sun Feb 01, 2004 1:36 am

I have managed to get my test routine working.
I have realised that the problem is that when i pass the variable called 'pixel' , the assembly routine sees that as an address, even although i defined it as a 'u16' and not a pointer.
To get around this i have loaded the address into r0 and then load r1 with the contents of address stored at r0.

Is there a faster and more direct way of getting variables into registers in assembly?

Thanks
GbaWIZ

#15842 - poslundc - Sun Feb 01, 2004 2:59 am

gbawiz wrote:
I have managed to get my test routine working.
I have realised that the problem is that when i pass the variable called 'pixel' , the assembly routine sees that as an address, even although i defined it as a 'u16' and not a pointer.


OK, the thing to realize is that when you get into assembler, it doesn't care what kinds of types and whatnot you were using in C. When you make a variable global, the address of that variable is provided to the linker. Think about it; in the following code:

Code:
   ldr   r0, .L_GLOBALS
   bx   lr
.L_GLOBALS:
   .word   myGlobalVariable


If myGlobalVariable is an imported variable from C, the variable itself won't actually exist at the memory address .L_GLOBALS, but somewhere else in memory that has been determined by the linker. What will instead be put there is the address of myGlobalVariable (or a pointer to it).

Quote:
To get around this i have loaded the address into r0 and then load r1 with the contents of address stored at r0.


That's how it's done. (And also what I showed you in my earlier example. <wink>)

Quote:
Is there a faster and more direct way of getting variables into registers in assembly?


If you want to you can define your global variables in proximity to your ASM code, and then refer to it as an extern global in all your C code.

Code:
   .align 2
   .global myGlobalVariable
   mov      r0, #SOMEVALUE
   str      r0, myGlobalVariable   @ change the variable's value
   @ etc. etc.
   bx      lr
myGlobalVariable:
   .word   0x12345678   @initial variable value goes here

Then in your C code would have the following definition:

extern int      myGlobalVariable;


This is kind of a hacky way to do things, though. It's very rare that the extra load statment will make all that much difference, and it would probably be considered better practice to keep your global variables defined in your C code.

Dan.

#15873 - FluBBa - Mon Feb 02, 2004 9:29 am

Did anybody even read what DekuTree64 wrote?

The first 4 arguments to the function are the normal registers.

Just do it like this with your define:
extern void setpixel(u16 color);

@----- CODE START ----@
.global setpixel
setpixel:
ldr r1,=0x6000200
str r0, [r1]
bx lr @return;
@------ CODE END ----@

And it will set the pixel to the value of the first argument.
Gaaaah, how hard can it be.
_________________
I probably suck, my not is a programmer.

#15880 - poslundc - Mon Feb 02, 2004 3:15 pm

No offense, FluBBa, but it should be pretty obvious from the thread that the guy is interested in more than just how to pass a parameter to a function.

Dan.