Subroutines

From veswiki
Revision as of 13:50, 12 April 2022 by E5frog (talk | contribs)
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)
Jump to: navigation, search

The F8 has no internal stack for the program counter, so you must be careful when calling subroutines. Using PI/POP only works for one level of subroutines, because the return address for the first PI opcode will be overwritten by subsequent PI opcodes. Here's a single-level example:

  prog:
      ; ...code
      pi sub                  ; Pushes address of next instruction to PC1
                              ; address of sub is stored in PC0 (jump to subroutine)
      ; ...code continues
  sub:
      ; ... often used code
      pop                     ; Move return address from PC1 to PC0


To have 2 levels of subroutines, you can use the K register to save the first return address:

  prog:
      ; ...do something...
      pi sub1                 ; Address of next instruction stored in PC1
                              ; sub1 is stored in PC0 (jump to subroutine)
      ; ...do more...
  sub1:
      lr k,p                  ; Copy PC1 to K, original jump address to K
      ; ...do something...
      pi sub2                 ; Pushes address of next instruction to PC1
                              ; sub1 is stored in PC0 (jump to subroutine)
      ; ...do more...
      pk                      ; Store address of next instruction in PC1
                              ; Copy value in K to PC0 (jump back to main)
  sub2:
      ; ...do something...
      pop                     ; Move return address from PC1 to PC0

That's as deep as the processor allows you to go without writing additional code to save return addresses. In the Channel F BIOS, there are routines which create a simulated stack for the K register. The routine at $0107 (known as PUSHK or CALL) can push K to the stack and the routine at $011E (known as POPK or RTRN) can pop K from the stack. For example:

  prog:
      ; ...do something...
      pi sub1
      ; ...do more...
  sub1:
      lr k,p
      pi PUSHK
      ; ...do something...
      pi sub2
      ; ...do more...
      pi POPK
      pk
  sub2:
      lr k,p
      pi PUSHK
      ; ...do something...
      pi sub3
      ; ...do more...
      pi POPK
      pk
  sub3:
      ; ...do something...
      pop

By using PUSHK/POPK, you can have more than 2 levels of subroutine calls. However, a lot of overhead is added to the code by manipulating the stack. When inside a pushk/popk subroutine it's still possible to use plain pi/pop as it only affects PC0 and PC1. Whenever calling a subroutine one level deep, it's best to use the PI/POP combination; for two levels of subroutines, it's best to use the second example above. If using the K register in a subroutine only simple PI/POP is usable to get there, not to destroy contents of K.

;==================;
; Register K Stack ;
;==================;

; the K stack is an emulated stack using the register r59
; as a stack pointer, which holds the register number for
; the top of the stack, which first points at r40. when
; pushk is called, K is pushed to the first two registers
; on the stack, and the pointer is increased.
;
; the K stack can hold 9 copies of K (r40-58) before 
; the stack pointer itself is overwritten (r59)

;--------;
; Push K ;
;--------;

; pushes register K (r12-13) onto a stack using r59 as
; the stack pointer
;
; modifies: r7

pushk:
	; backup the ISAR
	lr	A, IS
	lr	7, A

	; get the top of the stack
	lisu	7
	lisl	3			; r59, stack pointer
	lr	A, S
	lr	IS, A			; load the referenced register
	; push K onto the stack
	lr	A, Ku
	lr	S, A			; push high byte of K
	lr	A, IS
	inc
	lr	IS, A			; increase ISAR (they could've used lr I, A)
	lr	A, Kl
	lr	S, A			; push low byte of K
	; adjust pointer to the top of the stack
	lr	A, IS
	inc
	lr	IS, A			; increase ISAR to the top of the stack
	lr	A, IS			; redundantly, get back the register number
	lisu	7
	lisl	3
	lr	S, A			; save the register number to the stack pointer

	; restore the ISAR
	lr	A, 7
	lr	IS, A

	; return from the subroutine
	pop

;--------;
; Pop K ;
;--------;

; retrieves a 16-bit value from the K stack and
; stores it in K, using r59 as the stack pointer
;
; modifies: r7
                
popk:
	; backup the ISAR
	lr	A, IS
	lr	7, A

	; retrieve K from the stack
	lisu	7
	lisl	3
	lr	A, S			; load the stack pointer
	ai	$ff			; "subtract" 1 to get the first register on the stack
	lr	IS, A			; set the ISAR to this register
	lr	A, S
	lr	Kl, A			; load lower byte of K 
	lr	A, IS
	ai	$ff			; get previous register (they could've used lr A, D)
	lr	IS, A
	lr	A, S
	lr	Ku, A			; load upper byte of K
	; adjust pointer to the top of the stack
	lr	A, IS
	lisu	7
	lisl	3
	lr	S, A			; save the register number to the stack pointer

	; restore the ISAR
	lr	A, 7
	lr	IS, A

	; return from the subroutine
	pop

;===================;



Also consider using macros- you have a lot more code space than the original Channel F programmers, so you might as well use it; the time you save can be considerable.

Blackbird is writing more efficient versions of PUSHK/POPK (Snippet:KStack). Another idea is to write a version that uses the Schach RAM at $2800 that MESS emulates. That would free up more scratchpad registers and possibly also be quicker.

Here's a trick from the Guide: if a subroutine will be called frequently, it's quicker to load its address into the K register and call it using PK than to use PI multiple times. You'll use 4.5 cycles instead of 6.5 cycles to do the same thing.

It's also possible to change the Program Counter (PC0) with "lr P,Q" but there's no opcode for the other direction, address could be copied from A to Q in two steps or K to Q in four steps via Accumulator one byte at the time.


See Also