During development the key drive was to ensure it was balanced. The call overhead is about 350 cycles and so each feature added had to be justified.
The program was developed using CBM Prg Studio.
Native Features
- Direct call to function address
Up to 127 bytes of parameters
Parameters accessed either inline (following the JSR) or a vector
Default Parameters ("fill in the blanks"!)
Just-In-Time code vector cache
CPU registers are automatically preserved
Internal function vectors are externally accessible
These were in a previous version, but were relegated to a supporting role
- List processing - but it can be hacked by jumping directly into the function manager code
Stack frame storage - can be done ad-hoc within functions
Data-types and expression evaluation engine - cool, but limited use
Functions can include alternative code for expansion hardware.
A global variable, CpuMem$, indicates what is available: 0 is a standard C64, 8 is REU, 4 is SCPU, 2 is SCPU+REU and 1 is SCPU+SuperRAM. The list is ordered by priority, with SCPU+SR highest. Each managed function has a header of information, and the first byte of that header describes what alternate code is available, if any. On the very first use of that function the Function Manager will match the descriptor value with that of CpuMem$ and sets the function code address to be used on all subsequent uses.
Define A Managed Function
It's simple. Add the header and decide where any parameters will be stored (zero page, within the function's code, some other address, it doesn't matter) and what the default parameters will be.
Example 1: The two bytes of parameters that are passed in from the caller are redirected to $00FB ready and waiting for the program to add them together.
Code: Select all
; -------------------------------
; Sample Function (19 bytes). Without codevec management and encapsulated parameter area
DoSum jsr FnMan
byte 0 ; Function Descriptor
byte 2 ; ParamLen
word $FB ; Param Vector
word $FB ; Default Param Vector
word @Code ; Code Vector Default
@Code clc
lda $FB
adc $FC
sta zResult$
rts
Example 2: This one is more verbose but does the same thing. Note that the simple declarations of addresses in the header allow a significant amount of memory to be pushed around the system without any effort on the part of the programmer. A few simple tricks are available too, such as pointing the Default Parameter address to the Parameter address to reuse values, or even pointing it at zA$ (CPU Accumulator storage) and having direct access to .A,.X.Y and .S at the time of the call . The code vector can be preset (High-byte anything other than 0) to avoid the initial code base determination, but it's not really necessary. Only if alternate code is available is there a need to add addresses to the code vector list.
Code: Select all
; -------------------------------
; Sample Function (32 bytes)
DoSum2 jsr FnMan
byte %00001000 ; Function Descriptor
byte 2 ; ParamLen
word @Params ; Param Vector
word @DefParams ; Default Param Vector
word 0 ; Cached Code Vector
word @Code ; Code Vectors
word @ReuCode
@Code clc
lda @Params
adc @Params + 1
sta zResult$
rts
@ReuCode jmp @Code
@Params byte 0,0
@DefParams byte 1,2
Call a managed function by using a JSR <function address>, followed by a control byte that has Bit 8 clear if the parameters follow, or set if the parameters are remote. The lower 7 bits of the control byte indicate how many bytes to pass into the function. And the call does not need to pass in the exact amount expected by the function because functions have default parameters to fill in the missing ones (this feature can be very useful).
Code: Select all
jsr DoSum
byte 2, 20, 22 ; Inline parameters comprising two bytes to be added together
lda zResult$
sta $0400
rts
Below is the manager code. It's still a work-in-progress, but fairly complete.
Code: Select all
; ------------------------------
; Function Manager
; ------------------------------
; ----------
; GLOBALS
; The CPU-Memory layout
; 0 = C64
; Bit 1 = SCPU + SuperRAM
; Bit 2 = SCPU + REU
; Bit 3 = SCPU
; Bit 4 = REU
CpuMem$ byte 0
zResFnMan$ = $80
; ------------------------------
; Function Manager
; ------------------------------
; ----------
; GLOBALS
zResult$ = zResult
; Reg Storage
zA = zResFnMan$ ; 1 byte
zX = zA + 1 ; 1 byte
zY = zX + 1 ; 1 byte
zp = zX + 1
; Results
zResVec = zP + 1 ; 2 bytes
zResult = zResVec + 2 ; 5 bytes
; Vectors
zCodeVec = zResult + 5 ; 2 bytes
zCallParVec = zCodeVec + 2 ; 2 bytes
zCallVec = zCallParVec + 2 ; 2 bytes
zFnVec = zCallVec + 2 ; 2 bytes
zDefParVec = zFnVec + 2 ; 2 bytes
zParVec = zDefParVec + 2 ; 2 bytes
zReturnVec = zParVec + 2 ; 2 bytes
; Storage
zParLen = zReturnVec + 2 ; 1 byte
zParCnt = zParLen + 1 ; 1 byte
zFnDesc = zParCnt + 1 ; 1 byte
zCallCtrl = zFnDesc + 1 ; 1 byte
zTB1$ = zCallCtrl + 1 ; 1 byte
; Initialise Function Manager
InitFnMan jsr InitResV
rts
; Initialise the Result Vector
InitResV lda zResult
sta zResVec
lda zResult + 1
sta zResVec + 1
rts
FnMan ; Store registers
sta zA
stx zX
sty zY
php
pla
sta zP
; Get Function Rtn Addr
pla
sta zFnVec
pla
sta zFnVec + 1
; Get Function Descriptor
ldy #1
lda (zFnVec),Y
sta zFnDesc
; Get Function Param Length
iny
lda (zFnVec),Y
sta zParLen
; Get Function Param Vector
iny
lda (zFnVec),Y
sta zParVec
iny
lda (zFnVec),Y
sta zParVec + 1
; Get Function Default Param Vector
iny
lda (zFnVec),Y
sta zDefParVec
iny
lda (zFnVec),Y
sta zDefParVec + 1
; Get Function Code Vector
iny
lda (zFnVec),Y
sta zCodeVec
iny
lda (zFnVec),Y
sta zCodeVec + 1 ; if 0 then not initialised
bne @SetVec
; CodeVec is clear so a JIT determination is required
iny ; Bump to point to Default Code Vector
; Get CPU-Mem support
lda zFnDesc ; Get Function Descriptor
beq @SetCodeVec ; No support for anything other than C64 Standard
sta zTB1$
lda CpuMem$
beq @SetCodeVec ; C64 Standard so fetch first code vector
; Select a code vector from a prioritised list
ldx #4 ; 4 iterations
@L1 lsr zTB1$
bcc @C1
iny ; bump to next
iny
lsr
bcs @SetCodeVec ; Supported!
bcc @C2
@C1 lsr ; Unsupported so lsr to the next bit position
@C2 dex
bne @L1
; No matching alternate code, so Reset to default code pointer
ldy #9
; Set new Function Code Vector
@SetCodeVec lda (zFnVec),Y
sta zCodeVec ; Store the Vec in case future FNMan versions require it, eg list processing
pha
iny
lda (zFnVec),Y
sta zCodeVec + 1
ldy #8
sta (zFnVec),Y ; The location of the Function's @CodeVec
dey
pla
sta (zFnVec),Y
; Set new Function vector
@SetVec lda zCodeVec
sta JsrVec + 1
lda zCodeVec + 1
sta JsrVec + 2
; Get Caller Vector
@GetCrVc pla
sta zCallVec
pla
sta zCallVec + 1
; Get Caller Control Mode (Inline or Vector)
ldy #1
lda (zCallVec),Y
tax
asl
lda #0
rol
sta zCallCtrl
; Get Caller Param Count
txa
and #127
sta zParCnt
; Set up parameter vector and return address
FixCall iny ; Point to first Data
lda zCallCtrl
bne @Vector
; Inline Parameters
@Inline tya
clc
adc zCallVec
sta zCallParVec
lda #0
adc zCallVec + 1
sta zCallParVec + 1
lda #2 ; Set Call Return Vec to ( Caller + 2 + ParameterCount )
clc
adc zParCnt
bne @C1 ; Always
; Address of Parameters
@Vector lda (zCallVec),Y
sta zCallParVec
iny
lda (zCallVec),Y
sta zCallParVec + 1
tya ; Set Call Return Vec to ( Caller + 4). .Y is correct address offset
@C1 clc
adc zCallVec
sta zReturnVec
lda #0
adc zCallVec + 1
sta zReturnVec + 1
; Write Caller params
WrtCall ldy zParCnt
dey
sty zTB1$
bmi @C1
@L1 lda (zCallParVec),Y
sta (zParVec),Y
dey
bpl @L1
; Fill remainder of Params using the Function's Default Params
@C1 ldy zParLen
cpy zParCnt
beq SetRet ; No defaults to be written
dey
bmi SetRet
@L2 lda (zDefParVec),Y ; Write defaults
sta (zParVec),Y
dey
cpy zTB1$
bne @L2
; Setup Return Vector
SetRet lda zReturnVec + 1
pha
lda zReturnVec
pha
; Store Registers
lda zA
pha
ldy zX
pha
lda zY
pha
lda zP
pha
JsrVec jsr $0000 ; Call function
; Restore registers
plp
pla
tay
pla
tax
pla
rts
; -------------------------------
; Sample Caller
;
; jsr Ex_Fn
; byte 2 ; Call Control + Param Count
; byte 5,5 ; Params
;
; -------------------------------
; Sample Function
;
; Fn jsr FnMan
; byte %00001000 ; Function Descriptor
; byte 2 ; ParamLen
; word @Params ; Param Vector
; word @DefParams ; Default Param Vector
; word @CodeVec ; Cached code vector
; word @Code, @ReuCode ; Code Vectors