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
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.)
No comments:
Post a Comment