It is currently Thu Dec 14, 2017 3:53 pm

All times are UTC - 7 hours





Post new topic Reply to topic  [ 36 posts ]  Go to page 1, 2, 3  Next
Author Message
PostPosted: Thu Apr 09, 2009 8:25 am 
Offline

Joined: Tue Mar 03, 2009 3:56 pm
Posts: 298
I've been wanting to create a function framework for ASM6 for a little while now, both so I could better understand the way programming languages like C work at the low level, and also use C-like functions inside my assembly programs :D

I did a bit of research, did some experimentation.. and ended up with a pretty decent collection of macros for function calls in ASM6! It seems to work pretty well, but any optimizations or bug fixes are appreciated

The values being passed are kept in a stack at $0500 - $05FF which is separate from the internal stack. This stack does not contain program pointers, just data and pointers to data. Program pointers are still handled using the internal stack. The separation of data and internal stacks make code less error prone and also increases the level of nesting possible.

In addition to passing parameters, you can allocate space in the stack for temporary local variables. This is much more flexible than trying to reuse a handful of global variables as reusable temporary variables.
http://supermariounlimited.com/wiki/C_Style_Functions

and here's a test case.. nothing exciting, just writes some values to memory. You can check out the contents of the stack at $0500 as well to see that everything is working as expected

Code:
   resetStack
   
loop:                  ; call functions over and over to test stack
   call foo, #$FF
   jmp loop:


foo:
   foo_x equ #1         ; name the first parameter using equ
   foo_y equ #2         ; name local variable
      
   alloca #1            ; allocate anoter byte of memory for temporary local variable
   
   lda #$EE
   staLocal foo_y         ; assign foo_y a value
   
   call bar, #$AA, #$BB   ; call another function
   
   ldaLocal foo_x      
   sta #$10               ; write foo_x to $10
   ldaLocal foo_y   
   sta #$11               ; write foo_y to $11
   rts               
   
bar:

   bar_a = #1            ; name the parameters
   bar_b = #2
   
   bar_c = #3            ; and local vars
   bar_d = #4
   
   alloca #2            ; allocate 2 bytes for local variables

   ; Must prepend # onto var names  since vars were named with =
   
   ldaLocal #bar_a      ; copy bar_a
   staLocal #bar_c      ; to bar_c
   sta #$20               ; and to $20
   
   ldaLocal #bar_b      ; copy bar_b
   staLocal #bar_d      ; to bar_d
   sta #$21               ; and to $21
   
   ldaLocal #bar_c      ; copy bar_c
   sta #$22               ; to $22
                     
   ldaLocal #bar_d      ; copy bar_d
   sta #$23               ; to $23

   rts


Last edited by frantik on Fri Apr 10, 2009 1:47 pm, edited 4 times in total.

Top
 Profile  
 
 Post subject:
PostPosted: Thu Apr 09, 2009 8:39 am 
Offline
User avatar

Joined: Mon Sep 27, 2004 8:33 am
Posts: 3715
Location: Central Texas, USA
Wouldn't it make more sense to keep the stack pointer in x at all times? Then code could access stack variables via $500-i,x, where i is the index of the local. For example, if it has three locals, it accesses them as $500,x $4FF,x and $4FE,x. That makes them much more convenient and efficient to use.


Top
 Profile  
 
 Post subject:
PostPosted: Thu Apr 09, 2009 9:00 am 
Offline

Joined: Tue Mar 03, 2009 3:56 pm
Posts: 298
Then you wouldn't be able to use X for anything else inside of your functions.

You can use the macro ldxLocalOffset to get the x offset of a particular variable from the stack location

also the stack goes from bottom to top, not top to bottom (ie $500, $501, $502) this would allow you to place the stack at $100 if you didn't have a lot of nesting


Top
 Profile  
 
 Post subject:
PostPosted: Thu Apr 09, 2009 10:14 am 
Offline
User avatar

Joined: Fri Nov 12, 2004 2:49 pm
Posts: 7314
Location: Chexbres, VD, Switzerland
I always tough the concept of using a parameter/local variable stack in assembly is very interesting. After all I have to admit most of the errors I had when writing my game engine in assmebly were that I had problem with my variables. Using a solid system would have spared me hours of annoying bug track.

I've had a system where I can use 4 "temp" variables plus some others semi-temp variables, both to store temporary data and to pass parameters, but the routines calling themselves have to agree which one is using which temp variables. If more varaibles are needed then I create actual variables for this specific purpose.

However, the resulting code is slower, and acessing the stack gets too slow it could become really limitating to work with such a system. Some improbement should be done to make this faster I guess.

_________________
Life is complex: it has both real and imaginary components.


Top
 Profile  
 
 Post subject:
PostPosted: Thu Apr 09, 2009 11:49 am 
Offline
User avatar

Joined: Fri Apr 03, 2009 10:55 pm
Posts: 47
Any reasons not to just use the same calling conventions as cc65 either the fastcall or the regular ABI?


Top
 Profile  
 
 Post subject:
PostPosted: Thu Apr 09, 2009 11:53 am 
Offline
User avatar

Joined: Mon Sep 27, 2004 8:33 am
Posts: 3715
Location: Central Texas, USA
frantik wrote:
Then you wouldn't be able to use X for anything else inside of your functions.

Even with your approach, you can't use X whenever you're accesing locals. The main benefit of being able to use X freely is speed, but allowing that slows access to stack variables.
Quote:
also the stack goes from bottom to top, not top to bottom (ie $500, $501, $502) this would allow you to place the stack at $100 if you didn't have a lot of nesting

If $100 bytes were enough for both stacks, you could just use the normal stack. That'd be faster.


Top
 Profile  
 
 Post subject:
PostPosted: Thu Apr 09, 2009 12:16 pm 
Offline

Joined: Tue Mar 03, 2009 3:56 pm
Posts: 298
blargg wrote:
Even with your approach, you can't use X whenever you're accesing locals. The main benefit of being able to use X freely is speed, but allowing that slows access to stack variables.


if you want you can load x with the location of the first local var. then if you don't touch X it will work like you're talking about (i think) but i wouldn't want to make that a rigid requirement.. if you call another function you would have to push x onto the stack anyways. i can see the advantage to making sure X set on function load though

Quote:
If $100 bytes were enough for both stacks, you could just use the normal stack. That'd be faster.


yeah probably, but then you'd have to implement a whole different function system ;)


the point of this collection of macros isn't to create the most efficient code possible, but rather to make developing easy as possible. it's also kind of a "proof of concept"... though eventually I would like to create some kind of higher level language which is converted into assembly and compiled with asm6

there's no reason why i did anything any particular way except that is the way that made sense to me. I could make the stack work "backwards" and perhaps if i had dealt with stacks often that would a more intuitive way of working with it.


Top
 Profile  
 
 Post subject:
PostPosted: Thu Apr 09, 2009 2:36 pm 
Offline
User avatar

Joined: Sun Sep 19, 2004 10:52 pm
Posts: 361
Location: UT
frantik wrote:
the point of this collection of macros isn't to create the most efficient code possible, but rather to make developing easy as possible. it's also kind of a "proof of concept"... though eventually I would like to create some kind of higher level language which is converted into assembly and compiled with asm6

Since you're exploring HLL ideas, you might find NESHLA interesting.


Top
 Profile  
 
 Post subject:
PostPosted: Thu Apr 09, 2009 5:16 pm 
Offline

Joined: Tue Mar 03, 2009 3:56 pm
Posts: 298
yeah i've been stealing some concepts from that project along with every other one i can get my hands on :D

NESHLA one is probably closest to what I'm working on, though I like making programming languages so I'm gonna continue working on my system

i've already simplified coding significantly.. here is the rest of the code for that test case above

Code:
include "nes.h"
include "macros.s"

iNES_header               ; Basic NROM header

org $C000

reset:
   initNES               ; initialize the NES

   [... insert test case ...]

irq:
vblank:
   rti

vectors vblank, reset, irq


Top
 Profile  
 
 Post subject:
PostPosted: Thu Apr 09, 2009 7:24 pm 
Offline

Joined: Tue Mar 03, 2009 3:56 pm
Posts: 298
hey blargg i thought about what you said and I at least managed to make it so most macros expect X to be the same as stackPtr, saving a lot of ldx stackPtr. if x is overwritten, you can just load it from stackPtr again

stackPtr always points to a value in the stack. That value is a pointer to the start of the local vars

I'm gonna try to make it so Y is always the pointer to the start of local vars

edit: well i got Y to always be the ptr to local vars, but now i have to think of a new way to return values lol


Top
 Profile  
 
 Post subject:
PostPosted: Thu Apr 09, 2009 9:27 pm 
Offline

Joined: Tue Mar 03, 2009 3:56 pm
Posts: 298
I updated the code significantly to improve efficiency. Register X is kept as the stack pointer and Register Y is the pointer to the Local variables. ldaLocal uses $500+i,x instead of adc and the other stuff.

I'm keeping the code on my wiki for now so I don't have to paste the code into the message board over and over

http://supermariounlimited.com/wiki/C_Style_Functions


Top
 Profile  
 
 Post subject:
PostPosted: Fri Apr 10, 2009 6:12 am 
Offline
User avatar

Joined: Mon Sep 27, 2004 8:33 am
Posts: 3715
Location: Central Texas, USA
Something that always points to the local variables for the current function is usually called the [stack] frame pointer. But it's unnecessary, as you can just adjust the stack as necessary and access locals off the stack pointer. Also, it seems much better to leave Y free and have X be reserved for something, because the (zp),y addressing mode is the most important to have available to user code all the time, rather than (zp,x).


Top
 Profile  
 
 Post subject:
PostPosted: Fri Apr 10, 2009 1:39 pm 
Offline

Joined: Tue Mar 03, 2009 3:56 pm
Posts: 298
you can access variables from the stack pointer, but if you allocate more space on the stack, then the relative position of a given variable will change. thats why i'm keeping track of both pointers in the registers.

i'm also keeping copies of the pointers in ZP so i can restore their values easily if either register is needed for other operations. I only really NEED to keep track of the stack ptr since i can the frameptr from the end of the stack.. but it's faster to pull it from ZP obviously

i even added optional parameters to functions which expect registers to be set so that the registers can be reset if need be. register Y is now only used when accessing local variables using macros.

but you could access them relative to the stack pointer if a) needed to use Y and couldn't restore it during a specific operation and b) you knew the position of the value relative to the stack pointer.

it seems like it's pretty flexible.. the only thing I don't like is having to remember to restore X/Y (less coding friendly) but it does save some lines of code. I could make it so the macros default to reloading the registers from the ZP values (more coding friendly but less efficient) but it's less common that they need to be reloaded

i'm working on a "fastcall" for simple functions which just use the registers too


Top
 Profile  
 
 Post subject:
PostPosted: Sat Apr 11, 2009 11:43 am 
Offline

Joined: Tue Mar 03, 2009 3:56 pm
Posts: 298
something which the function structure is actually useful for: multiplication!

i see now X might be better off holding the frameptr since some opcodes don't have a way of accessing memory with a Y offset

Code:
;-----------
; int multiply (char x, char y)
;
; Multiplies two bytes in memory using Russian peasant algorithm
;
; Return value in ldaReturn 3 and ldaReturn 4

multiply:

   defineLocalVars
      value1ptr   char
      value2ptr   char
   
      ret         int               ; return value
      temp        char               ; temporary counter
   endLocalVars
   
   alloca #3                        ; allocate 3 bytes of memory for local vars

   
   lda #$00                         ; clear temporary variables
   staLocal ret
   staLocal ret+1
   staLocal temp
   tya
   tax
   jmp +start:

-loop:
   
   asl #(stack + value1ptr), x      ; double first value
   rol #(stack + temp), x           ; using 16bit precision
   lsr #(stack + value2ptr), x      ; halve second vale
+start:
   ldaLocal value2ptr               ;
   and #01                          ; is new 2nd value an odd number?
   beq -loop:                       ;
   clc                              ; if so, add new 1st value to running total
   ldaLocal ret                     
   adc #(stack + value1ptr), x     
   staLocal ret                     
   ldaLocal ret+1               
   adc #(stack + temp), x       
   staLocal ret+1   
   ldaLocal value2ptr     
   cmp #01                          ; is 2nd value 1?  if so, we're done
   bne -loop:                       ; otherwise, loop

   rts


Top
 Profile  
 
 Post subject:
PostPosted: Sat Apr 11, 2009 12:53 pm 
Offline
User avatar

Joined: Fri Nov 12, 2004 2:49 pm
Posts: 7314
Location: Chexbres, VD, Switzerland
I just have a random trought, I don't know how usefull it can be.
If stack pointer is at $ff and the stack grows at $500, why not always store #$05 into $fe so that you can acess the stack 2 ways, the most optimal is choosen in function of what we want :

ldx StackPointer
lda $500,X

or

ldy #$00 (or whathever other value)
lda ($fe),Y

_________________
Life is complex: it has both real and imaginary components.


Top
 Profile  
 
Display posts from previous:  Sort by  
Post new topic Reply to topic  [ 36 posts ]  Go to page 1, 2, 3  Next

All times are UTC - 7 hours


Who is online

Users browsing this forum: No registered users and 5 guests


You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot post attachments in this forum

Search for:
Jump to:  
Powered by phpBB® Forum Software © phpBB Group