Function params & local vars using a stack in ASM6

Discuss technical or other issues relating to programming the Nintendo Entertainment System, Famicom, or compatible systems. See the NESdev wiki for more information.

Moderator: Moderators

frantik
Posts: 377
Joined: Tue Mar 03, 2009 3:56 pm

Function params & local vars using a stack in ASM6

Post by frantik »

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: Select all

	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.
User avatar
blargg
Posts: 3715
Joined: Mon Sep 27, 2004 8:33 am
Location: Central Texas, USA
Contact:

Post by blargg »

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.
frantik
Posts: 377
Joined: Tue Mar 03, 2009 3:56 pm

Post by frantik »

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
User avatar
Bregalad
Posts: 8056
Joined: Fri Nov 12, 2004 2:49 pm
Location: Divonne-les-bains, France

Post by Bregalad »

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.
Useless, lumbering half-wits don't scare us.
User avatar
Jon
Posts: 47
Joined: Fri Apr 03, 2009 10:55 pm
Contact:

Post by Jon »

Any reasons not to just use the same calling conventions as cc65 either the fastcall or the regular ABI?
User avatar
blargg
Posts: 3715
Joined: Mon Sep 27, 2004 8:33 am
Location: Central Texas, USA
Contact:

Post by blargg »

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.
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.
frantik
Posts: 377
Joined: Tue Mar 03, 2009 3:56 pm

Post by frantik »

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
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.
User avatar
loopy
Posts: 405
Joined: Sun Sep 19, 2004 10:52 pm
Location: UT

Post by loopy »

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.
frantik
Posts: 377
Joined: Tue Mar 03, 2009 3:56 pm

Post by frantik »

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: Select all

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
frantik
Posts: 377
Joined: Tue Mar 03, 2009 3:56 pm

Post by frantik »

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
frantik
Posts: 377
Joined: Tue Mar 03, 2009 3:56 pm

Post by frantik »

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
User avatar
blargg
Posts: 3715
Joined: Mon Sep 27, 2004 8:33 am
Location: Central Texas, USA
Contact:

Post by blargg »

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).
frantik
Posts: 377
Joined: Tue Mar 03, 2009 3:56 pm

Post by frantik »

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
frantik
Posts: 377
Joined: Tue Mar 03, 2009 3:56 pm

Post by frantik »

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: Select all

;-----------
; 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
User avatar
Bregalad
Posts: 8056
Joined: Fri Nov 12, 2004 2:49 pm
Location: Divonne-les-bains, France

Post by Bregalad »

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
Useless, lumbering half-wits don't scare us.
Post Reply