Save X and Y. By Lee Davison.

Introduction.

Not something that comes up very often. I needed some code that would run on an NMOS 6502, not destroy X or Y, return a value in A and was re-entrant, i.e. it could call itself without overwriting any values.

The code.

First we save all the registers. We're not really interested in the value of A but we need a byte on the stack.


	PHA			; make space for A			
	TYA			; copy Y
	PHA			; save Y
	TXA			; copy X
	PHA			; save X

Now we have to restore the registers. Because we don't have PHX/PLX or PHY/PLY we must use A to restore X and Y but we also don't want to overwrite A, we can't just push A onto the stack as that makes getting X and Y off something of a problem. What we do is to overwrite the value for A that is already on the stack and then just pop the registers as we normally would.


	TSX			; copy stack pointer
	STA	$0103,X		; write result to stacked A		
	PLA			; pull stacked X
	TAX			; restore X
	PLA			; pull stacked Y
	TAY			; restore Y
	PLA			; get result into A

This code is also usefull if you don't know which type of 6502 the code will be run on and you need it to work on all of them.

Update.

.. need it to work on all of them.

Or not as it turns out. There can be a case when the above code doesn't work and that is if the stack is nearly full when the code is entered. While this won't happen very often it could happen and code that works nearly all the time can be frustrating.

Let's look at what happens when it fails, first the code as it saves the registers ..

			 S	Address
	PHA		$02	$0102	; make space for A		
	TYA		$01	.....	; copy Y
	PHA		$01	$0101	; save Y
	TXA		$00	.....	; copy X
	PHA		$00	$0100	; save X
	<more code>	$FF	$01FF	; subroutine code	

In this case A was pushed to address $0102 but after pushing X and Y the stack is full and the stack pointer now points back to the top of the stack. Now when the code tries to save A and exit the following happens ..

			 X	Address
	TSX		$FF	.....	; copy stack pointer
	STA	$0103,X	$FF	$0202	; write result to stacked A	
			 S	Address
	PLA		$00	$0100	; pull stacked X
	TAX		$01	.....	; restore X
	PLA		$01	$0101	; pull stacked Y
	TAY		$02	.....	; restore Y
	PLA		$02	$0102	; get result into A

Oops! We just failed to return the value in A and trashed $0202 as well. The solution is to make X equal the stack pointer when A was first pushed and use this as the offset from the base of the stack page.


	TSX			; copy stack pointer
	INX			; increment X to ..
	INX			; .. equal stack pointer ..
	INX			; .. when A was pushed
	STA	$0100,X		; write result to stacked A		
	PLA			; pull stacked X
	TAX			; restore X
	PLA			; pull stacked Y
	TAY			; restore Y
	PLA			; get result into A

Now going through the code showing the register values ..

			 X	Address
	TSX		$FF	.....	; copy stack pointer
	INX		$00	.....	; increment X to ..
	INX		$01	.....	; .. equal stack pointer ..
	INX		$02	.....	; .. when A was pushed
	STA	$0100,X	$02	$0102	; write result to stacked A	
			 S	Address
	PLA		$00	$0100	; pull stacked X
	TAX		$01	.....	; restore X
	PLA		$01	$0101	; pull stacked Y
	TAY		$02	.....	; restore Y
	PLA		$02	$0102	; get result into A

This costs a few bytes and some clock cycles but will work even if the stack pointer should wrap as above.


Last page update: 1st February, 2005. e-mail me