Sunday, December 8, 2024

ALPP 02-34 -- Ascending the Right Island -- Frameless Examples (Single- & Split-stack): 68000

This should be the final bits of treasure I have to drag up from the bottom of the pool before I get back to I/O.

  Ascending the Right Island --
 Frameless Examples (Single- & Split-stack):
68000

(Title Page/Index)

 

Having worked through the 6809 versions of the stack frame example code with the stack frame code stripped out, let's continue full circle and look at the 68000 version with the stack frame code stripped out. 

Again, there is not a whole lot to say here that can't be seen fairly easily in the code. Let's start with the single-stack no-frame code, comparing it with the single-stack stack frame code for the 68000 and the same for the 6809 in a new window. You'll want to assemble this and trace through it, stopping appropriately to look at the registers, stack, and memory.

	OPT LIST,SYMTAB	; Options we want for the stand-alone assembler.
	MACHINE MC68000	; because there are a lot the assembler can do.
	OPT DEBUG	; We want labels for debugging.
	OUTPUT
***********************************************************************
*
* 16-bit addition as example of single-stack no-frame discipline on 68000
* with test code
* Joel Matthew Rees, October, November 2024
*
NATWID	EQU	4	; 4 bytes in the CPU's natural integer
HLFNAT	EQU	2	; half natural integer
*
*
	EVEN
LB_ADDR	EQU	*
ENTRY	BRA.W	START
	NOP		; A little buffer zone.
	NOP
A4SAVE	DS.L	1	; a place to keep A4 to A7 so we can return clean
A5SAVE	DS.L	1	; using it as pseudo-DP
A6SAVE	DS.L	1	; 
A7SAVE	DS.L	1	; SP
	DS.L	2	; gap
HPPTR	DS.L	1	; heap pointer (not yet managed)
HPALL	DS.L	1	; heap allocation pointer
	DS.L	2	; gap
FINAL	DS.L	1	; unused statically allocated variable
GAP1	DS.L	51	; gap, make it an even 256 bytes.
*
	DS.L	2	; a little bumper space
SSTKLIM	DS.L	16*8	; 16 levels of call, with room for stack frames
* 			; 68000 is pre-dec (pre-store-decrement) push
SSTKBAS	DS.L	4	; for canary return
	DS.L	2	; bumper
HBASE	DS.L	$1000	; heap space (not yet managing it)
HLIM	DS.L	2	; bumper
*
*
	EVEN
INISTKS	MOVEM.L	(A7)+,A0	; get the return address from the BIOS-provided stack
	LEA	LB_ADDR(PC),A3	; point to our process-local area
	MOVEM.L	A4-A7,A4SAVE-LB_ADDR(A3)	; Store away what the BIOS gives us.
	MOVE.L	A3,A5	; set up our local base (pseudo-DP) in A5
	LEA	SSTKBAS+4*NATWID-LB_ADDR(A5),A7	; set up our return stack
	PEA	STKUNDR(PC)	; fake return to stack underflow handler
	PEA	STKUNDR(PC)	; fake return to stack underflow handler
	LEA	HBASE-LB_ADDR(A5),A4	; as if we actually had a heap
	MOVE.L	A4,HPPTR-LB_ADDR(A5)
	MOVE.L	A4,HPALL-LB_ADDR(A5)
	JMP	(A0)		; return via A0
*

***
* Stack after entry when functions are called by MAIN
* with two parameters
* We will return result in D0:D1
* [STKUNDR ]
* [STKUNDR ]SSTKBAS
* [RETADR0 ] 
* [--------]
* [--------] 
* [PARAM2_1]
* [PARAM2_2]
* [RETADR1 ] <= SP 
*

* Signed 16 bit add to 32 bit result
* Why do this? Stack cell is 32-bit, parameters are 16.
* Handle sign overflow without losing precision.
* input parameters:
*   16-bit left, right in 32-bit cell on stack
* output parameter:
*   17-bit sum in 32-bit D1
ADD16S	MOVE.W	NATWID+HLFNAT(A7),D0	; right (16-bit only)
	EXT.L	D0
	MOVE.W	2*NATWID+HLFNAT(A7),D1	; add to left (16-bit only)
	EXT.L	D1
	ADD.L	D0,D1			; 32-bit result
	RTS		; return, *** all flags valid!! ***
*
* Unsigned 16 bit add to 32 bit result
* input parameters:
*   16-bit left, right in 32-bit cell on stack
* output parameter:
*   17-bit sum in 32-bit D1
ADD16U	CLR.L	D0
	MOVE.W	NATWID+HLFNAT(A7),D0	; right (16-bit only)
	CLR.L	D1
	MOVE.W	2*NATWID+HLFNAT(A7),D1	; add to left (16-bit only)
	ADD.L	D0,D1			; 32-bit result
	RTS		; return, *** all flags valid!! ***
*
* Etc.
*

***
* Stack after entry when functions are called by MAIN
* with two parameters (pointer and addend)
* We will return result in D0:D1
* [STKUNDR ]
* [STKUNDR ]SSTKBAS
* [RETADR0 ] 
* [VAR1_1--]
* [VAR1_2--] <= PARAM2_1
* [PARAM2_1]
* [PARAM2_2]
* [RETADR1 ] <= SP
* To show how to access caller's local variables through pointer
* instead of walking stack --
* Add 16-bit signed parameter
* to 32 bit caller's 32-bit internal variable.
* input parameter:
*   16-bit pointer to 32-bit integer
*   16-bit addend
* no output parameter:
ADD16SI	MOVE.W	NATWID+HLFNAT(A7),D1	; skip over return address
	EXT.L	D1
	MOVE.L	2*NATWID(A7),A0		; get pointer to (internal) variable
	ADD.L	D1,(A0)			; add to variable pointed to
	RTS		; return, *** all flags valid!! ***
*
*
***
* Stack on entry
* [STKUNDR ]
* [STKUNDR ]SSTKBAS
* [RETADR0 ] <= SP
*
MAIN	CLR.L	-(A7)	; 2 variables
	CLR.L	-(A7)
	MOVE.L	#$1234,-(A7)
	MOVE.L	#$CDEF,-(A7)
	BSR.W	ADD16U	; result in D1 should be $E023
	LEA	2*NATWID(A7),A7	; could reuse, instead
	MOVE.L	D1,-(A7)
	MOVE.L	#$8765,-(A7)
	BSR.W	ADD16S	; result in D1 should be $FFFF6788 (and carry set)
	LEA	2*NATWID(A7),A7	; drop the parameters
	MOVE.L	D1,(A7)	; store result in 2nd local variable
	PEA	(A7)
	MOVE.L	#$A5A5,-(a7)
	BSR.W	ADD16SI		; result in 2nd variable should be FFFF0D2D (Carry set)
	MOVE.L	2*NATWID(A7),FINAL-LB_ADDR(A5)	; store the result
	LEA	4*NATWID(A7),A7	; drop the parameters and locals
	RTS
*
***
* Stack at START:
* (what BIOS/OS gave us) <= SP (A7)
***
*
* Stack after initialization:
* [STKUNDR ]
* [STKUNDR ]SSTKBAS <= SP
***
*
START	BSR.W	INISTKS
*
	NOP
*
	BSR.W	MAIN
*
	NOP
*
DONE	NOP
ERROR	NOP		; stack underflow and ERROR skip DONE
STKUNDR	NOP
	MOVEM.L	A4SAVE-LB_ADDR(A5),A4-A7	; restore the monitor's A4-A7
	NOP
	NOP		; landing pad
	NOP
	NOP
* One way to return to the OS or other calling program
	CLR.W	-(A7)	; there should be enough room on the caller's stack
	TRAP	#1	;	quick exit
*

I have stepped through the code myself. It runs, puts the correct results where they are supposed to go, and restores the stack as it should.

Now let's look at it with a split-stack frameless discipline, comparing it with both the above in a new browser window and the split-stack stack frame version for the 68000, and with the split-stack version for the 6809. Assemble this one, as well, and step through it, watching memory and registers.

	OPT LIST,SYMTAB	; Options we want for the stand-alone assembler.
	MACHINE MC68000	; because there are a lot the assembler can do.
	OPT DEBUG	; We want labels for debugging.
	OUTPUT
***********************************************************************
*
* 16-bit addition as example of split-stack, frameless discipline on 68000
* with test code
* Joel Matthew Rees, October 2024
*
NATWID	EQU	4	; 4 bytes in the CPU's natural integer
HLFNAT	EQU	2	; half natural integer
*
*
	EVEN
LB_ADDR	EQU	*
ENTRY	BRA.W	START
	NOP		; A little buffer zone.
	NOP
A4SAVE	DS.L	1	; a place to keep A4 to A7 so we can return clean
A5SAVE	DS.L	1	; using it as pseudo-DP
A6SAVE	DS.L	1	; 
A7SAVE	DS.L	1	; SP
	DS.L	2	; gap
HPPTR	DS.L	1	; heap pointer (not yet managed)
HPALL	DS.L	1	; heap allocation pointer
	DS.L	2	; gap
FINAL	DS.L	1	; unused statically allocated variable
GAP1	DS.L	51	; gap, make it an even 256 bytes.
*
	DS.L	2	; a little bumper space
SSTKLIM	DS.L	16*2	; 16 levels of call, with room for frame pointers
* 			; 68000 is pre-dec (pre-store-decrement) push
SSTKBAS	DS.L	2	; for canary return
SSTKBMP	DS.L	2	; bumper
PSTKLIM	DS.L	16*4	; roughly 16 levels of call
PSTKBAS	DS.L	2	; bumper
HBASE	DS.L	$1000	; heap space (not yet managing it)
HLIM	DS.L	2	; bumper
*
*
	EVEN
INISTKS	MOVE.L	(A7)+,A0	; get the return address from the other (BIOS) stack
	LEA	LB_ADDR(PC),A3
	MOVEM.L	A4-A7,A4SAVE-LB_ADDR(A3)	; Store away what the BIOS gives us.
	MOVE.L	A3,A5	; set up our local base (pseudo-DP)
	LEA	SSTKBMP-LB_ADDR(A5),A7	; set up our return stack
	LEA	PSTKBAS-LB_ADDR(A5),A6	; set up our parameter stack
	PEA	STKUNDR(PC)	; fake return to stack underflow handler
	PEA	STKUNDR(PC)	; fake return to stack underflow handler
	LEA	HBASE-LB_ADDR(A5),A4	; as if we actually had a heap
	MOVE.L	A4,HPPTR-LB_ADDR(A5)
	MOVE.L	A4,HPALL-LB_ADDR(A5)
	JMP	(A0)		; return via A0
*

***
* Return stack when functions are called by MAIN
* Return stack on entry:
* [STKUNDR ]
* [STKUNDR ]SSTKBAS
* [RETADR0 ] 
* [RETADR1 ] <= RSP
*
* Parameter stack when called by MAIN
* with two 16-bit parameters,
* [<unknown>]
* [32:VAR1_1--]
* [32:VAR1_2--]
* [16:PARAM2_1]
* [16:PARAM2_2] <= PSP
*

* Signed 16 bit add with 32 bit result
* Handle sign overflow without losing precision.
* input parameters:
*   16-bit left, right in 16-bit cells
* output parameter:
*   17-bit sum in 32-bit cell
ADD16S	MOVEM.W	(A6)+,D0/D1	; D0 lowest, but 16-bit sign extends!
*	EXT.L	D0		; right
*	EXT.L	D1		; left
	ADD.L	D0,D1		; add right to left
	MOVE.L	D1,-(A6)
	RTS		; return, *** all flags valid!! ***
*
* Unsigned 16 bit add to 32 bit result
* input parameters:
*   16-bit left, right in 16-bit cells
* output parameter:
*   17-bit sum in 32-bit cell
ADD16U	CLR.L	D0
	CLR.L	D1
*	MOVEM.W	(A6)+,D0/D1	; D0 lowest, but 16-bit sign extends!
	MOVE.W	(A6)+,D0	; right
	MOVE.W	(A6)+,D1	; left
	ADD.L	D0,D1		; add right to left
	MOVE.L	D1,-(A6)
	RTS		; return, *** all flags valid!! ***
*
* Etc.
*

***
* Parameter stack when called by MAIN
* with two parameters, 32-bit pointer and 16-bit addend
* [32:VAR1_1--]
* [32:VAR1_2--] <= PARAM2_1 
* [32:PARAM2_1]
* [16:PARAM2_2] <= PSP

* Instead of walking the stack, pass in a pointer --
* Add 16-bit signed parameter
* to caller's 2nd 32-bit internal variable.
* input parameter:
*   32-bit pointer to 32-bit integer
*   16-bit addend
* no output parameter:
ADD16SI	MOVE.W	(A6)+,D1		; addend
	EXT.L	D1
	MOVE.L	(A6)+,A0		; get caller's internal variable pointer
	ADD.L	D1,(A0)	; add to caller's 2nd variable
	RTS		; return, *** all flags valid!! ***
*
*
***
* Return stack on entry:
* [STKUNDR ]
* [STKUNDR ]SSTKBAS
* [RETADR0 ] <= RSP
*
* Parameter stack after local allocation
* [<unknown>]
* [VAR1_1--]
* [VAR1_2--] <= PSP
*
MAIN	CLR.L	-(A6)		; allocate and initialize
	CLR.L	-(A6)		; allocate and initialize
	MOVE.W	#$1234,-(A6)
	MOVE.W	#$CDEF,-(A6)
	BSR.W	ADD16U	; result on parameter stack should be $E023
	LEA	HLFNAT(A6),A6	; adjust to 16 bit, could be optimized out
	MOVE.W	#$8765,-(A6)
	BSR.W	ADD16S	; result on parameter stack should be $FFFF6788 (and carry set)
	MOVE.L	(A6),NATWID(A6)	; save in local
	LEA	NATWID(A6),A0
	MOVE.L	A0,(A6)		; save the pointer.
	MOVE.W	#$A5A5,-(a6)	; push the addend.
	BSR.W	ADD16SI		; result in 2nd variable should be FFFF0D2D (Carry set)
	MOVE.L	(A6),FINAL-LB_ADDR(A5)	; store the result
	RTS
*
***
* Stack at START:
* (what BIOS/OS gave us) <= RSP (A7)
***
* (who knows?) <= PSP (A6)
***
*
***
* Return stack will always be in pairs:
* [RETADRNN  ]
* [CALLERFMNN]
*
* Return stack after initialization:
* [STKUNDR ]
* [STKUNDR ]SSTKBAS <= RSP
*
* Parameter stack after initialization, mark:
* [<unknown] <= PSP,FP==<EMPTYP>
*
START	BSR.W	INISTKS
*
	NOP
*
	BSR.W	MAIN
*
	NOP
*
DONE	NOP
	NOP		; landing pad
ERROR	NOP
STKUNDR	NOP
	MOVE.L	(A7)+,A4
	MOVEM.L	A4SAVE-LB_ADDR(A5),A4-A7	; restore the monitor's A4-A7
	NOP
	NOP		; landing pad
	NOP
	NOP
* One way to return to the OS or other calling program
	CLR.W	-(A7)	; there should be enough room on the caller's stack
	TRAP	#1	;	quick exit
*

Man, I'm worn out. On the other hand, I think this detour will help me focus as we progress through the I/O examples, starting with binary numeric output.

(It definitely helped me figure out some daydreams about extending the 6801, if you're interested.)


(Title Page/Index)


No comments:

Post a Comment