This page is a mirror of Tepples' nesdev forum mirror (URL TBD).
Last updated on Oct-18-2019 Download

Getting the function parameters in Assembly

Getting the function parameters in Assembly
by on (#155786)
I'm planning to write one or two functions in Assembly that use parameters. That's why I'd like to know exactly how this works:

Let's say I have this function declaration in C:
Code:
unsigned int DoSomething(const unsigned char *pointer1, unsigned char *pointer2, unsigned int intValue, unsigned char byteValue);


What do I have to write in my assembly code to retrieve each of the four parameters (which are seven bytes here) and where do I have to put the return value?

Code:
_DoSomething:
    ; ???
    RTS


And what would be different if I used __fastcall__?
Re: Getting the function parameters in Assembly
by on (#155788)
Fastcall means that the last (rightmost) parameter goes into A (or A+X if 16 bit). Any other parameters go on the C-stack. Without fastcall, all parameters go on the C-stack.

See:
http://wiki.cc65.org/doku.php?id=cc65:parameter_passing_and_calling_conventions
http://wiki.cc65.org/doku.php?id=cc65:parameter_and_return_stacks

You can recover variables from the C-stack by using CRT routines like pusha/popa, pushax/popax that you can jsr to.

If you're not sure about how it does something, you can always write an equivalent function in C, and compile it to see how it uses the stack routines to get the parameters. Then just use that as a guide for your assembly version of the same thing, making sure to leave the stack in the same state.


I usually try to avoid the issue altogether, because I don't like the C-stack. My assembly functions will be void (to avoid having to return a value on the C-stack), and take at most 1 parameter so that fastcall is sufficient to avoid any need to use the C-stack.
Re: Getting the function parameters in Assembly
by on (#155790)
Yeah, the problem is: The only reason why I want to write this Assembly function is because passing pointers in a C function is slow. How shall I write an equivalent of memcpy without parameters?
Re: Getting the function parameters in Assembly
by on (#155793)
DRW wrote:
How shall I write an equivalent of memcpy without parameters?

I think I made a suggestion for that one already: http://forums.nesdev.com/viewtopic.php?p=155643#p155643

The same idea with two parameters is a trivial change to that code:

Code:
// c declaration
void fastcall copy(unsigned char* dest, unsigned char* src, unsigned char count);

; assembly
_copy:
   pha ; count in A due to fastcall
   jsr popax ; recover src from stack
   sta ptr0+0
   stx ptr0+1
   jsr popax ; recover dest from stack
   sta ptr1+0
   stx ptr1+1
   pla
   tay ; count now in Y
   jmp @next
   @loop:
      dey
      lda (ptr0), Y
      sta (ptr1), Y
   @next:
      cpy #0
      bne @loop
   rts


The one in the other thread using a macro to set up statics instead of parameter has slightly less overhead (parameters don't need to be pushed to the stack, or popped from them), so calls will be slightly faster and slightly smaller. Otherwise it's identical to this version using the C-stack. You could even use lowercase letters for the macro and it would look identical, externally.
Re: Getting the function parameters in Assembly
by on (#155795)
rainwarrior wrote:
I think I made a suggestion for that one already: http://forums.nesdev.com/viewtopic.php?p=155643#p155643

Sure, but didn't we then find out that ptr0=param_src; in C is just as slow as using a function parameter?

I'll have a look at your new code, but it's not the same that you suggested last time.

Last time, your function declaration was merely:
extern void fastcall copy8(unsigned char len);

And the code was:
Code:
_copy8:
   tay ; fastcall means len parameter is in A
   jmp @next
   @loop:
      dey
      lda (ptr0), Y
      sta (ptr1), Y
   @next:
      cpy #0
      bne @loop
   rts


And this one didn't solve my original problem (the fact that passing pointers in C is uber-slow and therefore, the function definition needs to be written in Assembly).
Also, it didn't use the stack at all, so I couldn't learn from it how to use parameters in Assembly (apart from the one fastcall parameter).

So, please be aware that I didn't ignore your last suggestion. It just doesn't do what I need, so I still had to ask.
But the current function will probably be helpful. Thanks for it.
Re: Getting the function parameters in Assembly
by on (#155797)
DRW wrote:
Sure, but didn't we then find out that ptr0=param_src; in C is just as slow as using a function parameter?

When exactly did we find that out? Static parameters are faster than using the stack.
Code:
; static parameter call:
lda param
sta mystatic
jsr myfunc

; function parameter call:
lda param
jsr pusha ; this is a lot more work than a sta (also we have to jsr popa to get it back inside the function)
jsr myfunc


DRW wrote:
I'll have a look at your new code, but it's not the same that you suggested last time.

It's identical except it uses the function parameters instead of statics. Calling it would look the same in code:
Code:
COPY8(dest,src,25); // previous suggestion
copy(dest,src,25); // new suggestion
// you could define the macro from the previous suggestion with lowercase letters and the call would look identical


DRW wrote:
And this one didn't solve my original problem (the fact that passing pointers in C is uber-slow and therefore, the function definition needs to be written in Assembly).

The new example doesn't solve any problem the original version didn't. The only thing that is different here is it's using the C-stack for parameters (which is actually slower, and no real advantage other than a few less lines in a header declaring the function).


The actual speed problem with C code you wrote was not really to do with parameters, as we determined, but about how CC65 handles certain uses of variables and pointers, apparently. It was just generating really bad code for what you wrote; so the only options are to do it a different way in C, or to rewrite it in assembly.
Re: Getting the function parameters in Assembly
by on (#155798)
rainwarrior wrote:
When exactly did we find that out?

O.k., maybe the mere assigment of the pointer isn't the thing that made it slow. Yes, I see, it's the access of the pointer inside the function.

But nevertheless, we can be sure of one thing: Passing parameters vs. using global variables is not the issue here. This mere distinction doesn't solve anything.

Therefore, when I write a better general purpose function in Assembly, I can just as well use parameters instead of fiddling with global variables and macros. Since the question of parameters vs. globals is not the factor that decides between quick and slow.

I.e. the old function might have solved the issue that I had back then. (I didn't try it out since I used memcpy instead.) But it would still not have been the answer to my current question because if I rewrite a generic memcpy function myself, I would still want to use actual parameters nevertheless, not global variables.

rainwarrior wrote:
It's identical except it uses the function parameters instead of statics.

But if my thread topic is explicitly "How do I use function parameters in Assembly", then the fact that a new function differs in nothing but in the fact that it uses function parameters makes all the difference.
Re: Getting the function parameters in Assembly
by on (#155801)
DRW wrote:
I.e. the old function might have solved the issue that I had back then. (I didn't try it out since I used memcpy instead.) But it would still not have been the answer to my current question because if I rewrite a generic memcpy function myself, I would still want to use actual parameters nevertheless, not global variables.


Both use the global static variables in the same way, the only difference internally is that the assignment of ptr0/ptr1 happens at call time for the "static passing" version, and it happens inside the function for the "stack passing" version. Both functions use those two static variables, and the "stack passing" version is merely wasting its time putting them on the stack and taking them directly off the stack again before putting them in the two static variables.

They are both valid methods of having parameters to a function. Aside from the speed difference, the code difference is a few extra lines of assembly for the "stack passing" method, versus a few extra lines of declaration in a header for the "static passing" method.

The reason you don't see static passing as much is mainly just that on most platforms, local and stack based variables are cheap and fast. This is not the case for 6502, and it's doubly not the case for CC65 which uses its own software C-stack instead of the hardware stack (not sure why it has this design).

So, in the absence of a performance disadvantage, the stack passing idiom is a better default, just because it keeps all your variables locally contained, which is a nice feature, but it's really a very specific/situational advantage. With CC65 you are trading performance for that advantage, though. Are you favouring stack based parameters because you actually need local encapsulation, or is it just because it "feels more like C"?


I'm not saying that you should always do it one way or the other, and of course you should do what seems best for you. I'm just trying to explain why in this specific case the stack passing method seems wholly inferior to me.


Quote:
But if my thread topic is explicitly "How do I use function parameters in Assembly", then the fact that a new function differs in nothing but in the fact that it uses function parameters makes all the difference.

Yes, I wrote the modified version entirely as a demonstration of how to use C-stack based parameters from assembly.
Re: Getting the function parameters in Assembly
by on (#155802)
Yeah, o.k., you might have a point here. When I use parameters, it writes to the stack and then reads from the stack. Global variables just need to be passed and that's it.

No, I don't actually need encapsulation since it's a self-contained program and not a library for other people to use. So, I'll probably do the version with global variables after all.

Is it safe to use CC65's ptr1 and ptr2 here? Or should I declare my own pointer variables for my own functions?

I assume since __fastcall__ can store one parameter in A, using the length as a parameter is better than assigning it to a global variable as well?
Re: Getting the function parameters in Assembly
by on (#155803)
I can't remember what CC65's set of internally used globals consists of, or what they're named, but I would not presume that it is safe to use any of them in a custom assembly function. If it has a temporary pointer variable or two, I don't recommend trying to use them in your own assembly.

The variables ptr0 and ptr1 were explicitly reserved. (See original example.) I left that stuff out from the revised example because I didn't think it was an important detail to write out a second time.

With fastcall, yes the last parameter is already in a register, so probably no reason to stick it in a static in most cases. In this particular case it was convenient to transfer it to an index register (Y) and just keep it there as a "local" variable within the assembly subroutine.
Re: Getting the function parameters in Assembly
by on (#155804)
Oh, I see, you were referring to ptr1/ptr2 as used in memcpy.s.

Those might actually be safe to use, because I doubt the compiler depends on the internals of memcpy.s in that way, but it might be playing with fire to touch any of CC65's internal variables. I don't think I'd risk it, personally, but I suspect they would be safe.

Edit: the zeropage temporaries are all defined in zeropage.s.

I think all of the 8 ptr/tmp variables are "safe" to use in an assembly function as long as it is not calling other CRT functions that might use them. That might be a difficult "if" to evaluate, though. For example, I don't know if any of the stack based functions would use them. Like if you need to "jsr pushax", for example, you have to be sure pushax isn't going to use one of those temporaries. Probably most of the ones you'd use would not, but you'd have to look at them on a case by case basis.
Re: Getting the function parameters in Assembly
by on (#155806)
rainwarrior wrote:
The variables ptr0 and ptr1 were explicitly reserved. (See original example.)

Yes, I knew that your own example didn't use the CC65 pointers.

Alright, when I'm at home again, I'll replace the memcpy call with a custom function. So, I don't have to rely on the C standard library.
Thanks again for your help and for your hints.

Edit:

rainwarrior wrote:
Oh, I see, you were referring to ptr1/ptr2 as used in memcpy.s.

Those might actually be safe to use, because I doubt the compiler depends on the internals of memcpy.s in that way, but it might be playing with fire to touch any of CC65's internal variables.

I have worries that ptr1 and ptr2 (which are generic variables that are always included, they don't belong specifically to memcpy. For example, every generated source file always has .importzp ptr1) might be used internally in the automatically generated Assembly code of my C functions:
Code:
void MyFunction()
{
    // Do something
    ...

    MyMemCpyMacro(destination, source, length);

    // Do something else
    ...
}

Can I be sure that the compiler won't use prt1 in the location where I wrote "Do something" and then it needs the same value in "Do something else", therefore me using it for the MyMemCpy function overwrites the needed value?

Actually, it has to be safe because otherwise, the standard memcpy function would produce a bug as well in this case, right?

On the other hand, the name is ptr1, not _ptr1. So, I probably won't be able to assign that variable from within C anyway.
Re: Getting the function parameters in Assembly
by on (#155807)
Sorry, I was editing my response to answer that as you were typing:
Quote:
The zeropage temporaries are all defined in zeropage.s.

I think all of the 8 ptr/tmp variables are "safe" to use in an assembly function as long as it is not calling other CRT functions that might use them. That might be a difficult "if" to evaluate, though. For example, I don't know if any of the stack based functions would use them. Like if you need to "jsr pushax", for example, you have to be sure pushax isn't going to use one of those temporaries. Probably most of the ones you'd use would not, but you'd have to look at them on a case by case basis.


I do not believe the compiler knows anything special about the contents of CRT functions, so you're probably safe to assume that it can't rely on them being preserved when making generated C code (but someone might theoretically write a "clever" optimizer that tries to rely on this in the future, or make some other compiler change that relies on their preservation within generated C code). The more relevant problem is whether you will call CRT functions within your assembly function that clobber them while you're trying to use them.
Re: Getting the function parameters in Assembly
by on (#155808)
rainwarrior wrote:
The more relevant problem is whether you will call CRT functions within your assembly function that clobber them while you're trying to use them.

I'm not planning to use any functions from the C standard library.

As for the low level functions like popax: Not in this case, but I cannot exclude it in the future. But probably not.

But as I said: Using ptr1 will probably not be possible anyway, right?
While I would have access to it in the actual Assembly function, the fact that the name lacks an underscore makes it impossible for me to do this:
Code:
extern unsigned char *ptr1, *ptr2;
// Assembly name would have to be _ptr1/_ptr2, not ptr1/ptr2.

void __fastcall__ Copy_(unsigned char len);
#define Copy(dest, src, len)\
{\
    ptr1 = dest;\
    ptr2 = src;\
    Copy_(len);\
}
Re: Getting the function parameters in Assembly
by on (#155809)
Hmm, yes I suppose that method would not work. If you really wanted to, though, you could access assembly variables directly with an inline assembly statement in that macro.
Re: Getting the function parameters in Assembly
by on (#155812)
rainwarrior wrote:
... and it's doubly not the case for CC65 which uses its own software C-stack instead of the hardware stack (not sure why it has this design).

I think the design reason is the limited size of the 6502 stack (not just my speculation, I think I read this somewhere).
Re: Getting the function parameters in Assembly
by on (#155813)
rainwarrior wrote:
If you really wanted to, though, you could access assembly variables directly with an inline assembly statement in that macro.

Nah. I try to avoid inline Assembly.
I'll just declare two own pointer variables. One of them will be declared as unsigned char *Pointer; in C and one as const unsigned char *ReadOnlyPointer; (for const unsigned char[] assignments.)