also provide a X16-style JSRFAR implementation for the C64. Enable callfar() and callfar2() on the C64 and C128.

This commit is contained in:
Irmen de Jong 2024-11-05 19:01:27 +01:00
parent 459e9f8f3b
commit 7fd3e9bb7d
6 changed files with 116 additions and 49 deletions

View File

@ -208,15 +208,22 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
}
private fun funcCallFar(fcall: PtBuiltinFunctionCall, resultRegister: RegisterOrPair?) {
if(asmgen.options.compTarget.name != "cx16")
throw AssemblyError("callfar only works on cx16 target at this time")
val targetName = asmgen.options.compTarget.name
if(targetName !in arrayOf("cx16", "c64", "c128"))
throw AssemblyError("callfar only works on cx16, c64 and c128 targets at this time")
val jsrfar = when(targetName) {
"cx16" -> "cx16.JSRFAR"
"c64" -> "c64.x16jsrfar"
"c128" -> "c128.x16jsrfar"
else -> TODO("jsrfar routine")
}
val constBank = fcall.args[0].asConstInteger()
val constAddress = fcall.args[1].asConstInteger()
if(constBank!=null && constAddress!=null) {
asmgen.assignExpressionToRegister(fcall.args[2], RegisterOrPair.AY) // uword argument
asmgen.out("""
jsr cx16.JSRFAR
jsr $jsrfar
.word ${constAddress.toHex()}
.byte $constBank""")
} else {
@ -226,7 +233,7 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
asmgen.out(" sta (+)+0 | sty (+)+1")
asmgen.assignExpressionToRegister(fcall.args[2], RegisterOrPair.AY) // uword argument
asmgen.out("""
jsr cx16.JSRFAR
jsr $jsrfar
+ .word 0
+ .byte 0""")
}
@ -238,8 +245,9 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
}
private fun funcCallFar2(fcall: PtBuiltinFunctionCall, resultRegister: RegisterOrPair?) {
if(asmgen.options.compTarget.name != "cx16")
throw AssemblyError("callfar2 only works on cx16 target at this time")
val targetName = asmgen.options.compTarget.name
if(targetName !in arrayOf("cx16", "c64", "c128"))
throw AssemblyError("callfar2 only works on cx16, c64 and c128 targets at this time")
fun assignArgs() {
fun assign(value: PtExpression, register: Char) {
@ -260,12 +268,18 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
TODO("callfar2: support non-const argument values")
}
val jsrfar = when(targetName) {
"cx16" -> "cx16.JSRFAR"
"c64" -> "c64.x16jsrfar"
"c128" -> "c128.x16jsrfar"
else -> TODO("jsrfar routine")
}
val constBank = fcall.args[0].asConstInteger()
val constAddress = fcall.args[1].asConstInteger()
if(constBank!=null && constAddress!=null) {
assignArgs()
asmgen.out("""
jsr cx16.JSRFAR
jsr $jsrfar
.word ${constAddress.toHex()}
.byte $constBank""")
} else {
@ -275,7 +289,7 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
asmgen.out(" sta (+)+0 | sty (+)+1")
assignArgs()
asmgen.out("""
jsr cx16.JSRFAR
jsr $jsrfar
+ .word 0
+ .byte 0""")
}

View File

@ -54,27 +54,11 @@ internal class FunctionCallAsmGen(private val program: PtProgram, private val as
)
}
"c64" -> {
// a bit bloated because it has to retain the status flags and A register
asmgen.out("""
; start doing a jsr to another bank
php
pha
lda $01
sta +
lda #$bank
sta $01
pla
plp
jsr $subAsmName
php
pha
lda +
sta $01
pla
plp
jmp ++
+ .byte 0 ; original banks
+""")
jsr c64.x16jsrfar
.word $subAsmName ; ${sub.address!!.second.toHex()}
.byte $bank"""
)
}
"c128" -> {
asmgen.out("""

View File

@ -332,6 +332,68 @@ inline asmsub getbanks() -> ubyte @A {
}}
}
asmsub x16jsrfar() {
%asm {{
; setup a JSRFAR call (using X16 call convention)
sta P8ZP_SCRATCH_W2 ; save A
sty P8ZP_SCRATCH_W2+1 ; save Y
php
pla
sta P8ZP_SCRATCH_REG ; save Status
pla
sta P8ZP_SCRATCH_W1
pla
sta P8ZP_SCRATCH_W1+1
; retrieve arguments
ldy #$01
lda (P8ZP_SCRATCH_W1),y ; grab low byte of target address
sta _jmpfar+1
iny
lda (P8ZP_SCRATCH_W1),y ; now the high byte
sta _jmpfar+2
iny
lda (P8ZP_SCRATCH_W1),y ; then the target bank
sta P8ZP_SCRATCH_B1
; adjust return address to skip over the arguments
clc
lda P8ZP_SCRATCH_W1
adc #3
sta P8ZP_SCRATCH_W1
lda P8ZP_SCRATCH_W1+1
adc #0
pha
lda P8ZP_SCRATCH_W1
pha
lda $01 ; save old ram banks
pha
; set target bank, restore A, Y and flags
lda P8ZP_SCRATCH_REG
pha
lda P8ZP_SCRATCH_B1
jsr banks
lda P8ZP_SCRATCH_W2
ldy P8ZP_SCRATCH_W2+1
plp
jsr _jmpfar ; do the actual call
; restore bank without clobbering status flags and A register
sta P8ZP_SCRATCH_W1
php
pla
sta P8ZP_SCRATCH_B1
pla
jsr banks
lda P8ZP_SCRATCH_B1
pha
lda P8ZP_SCRATCH_W1
plp
rts
_jmpfar jmp $0000 ; modified
}}
}
sub get_vic_memory_base() -> uword {
; one of the 4 possible banks. $0000/$4000/$8000/$c000.

View File

@ -194,15 +194,15 @@ call (address) -> uword
But because it doesn't handle bank switching etcetera by itself,
it is a lot faster than ``callfar``. And it works on other systems than just the Commander X16.
callfar (bank, address, argumentword) -> uword ; NOTE: specific to cx16 target for now
Calls an assembly routine in another bank on the Commander X16 (using its ``JSRFAR`` routine)
callfar (bank, address, argumentword) -> uword
Calls an assembly routine in another bank.
Be aware that ram OR rom bank may be changed depending on the address it jumps to!
The argumentword will be loaded into the A+Y registers before calling the routine.
The uword value that the routine returns in the A+Y registers, will be returned.
NOTE: this routine is very inefficient, so don't use it to call often. Set the bank yourself
or even write a custom tailored trampoline routine if you need to. Or use ``call`` if you can.
callfar2 (bank, address, argA, argX, argY, argCarry) -> uword ; NOTE: specific to cx16 target for now
callfar2 (bank, address, argA, argX, argY, argCarry) -> uword
Identical to ``callfar``, except here you can give arguments not only for AY,
but for each of the A, X and Y registers (each an ubyte) and the Carry status bit as well (a boolean).

View File

@ -1,8 +1,6 @@
TODO
====
in translateFunctionCall(), make the JSRFAR bloat code for the C64 into a subroutine like the kernal JSRFAR on the X16
fix that on C64 when program ends halfway in basic address space $a000+ the sysinit code that banks out the basic Rom
is actually not reachable yet because it's at the end of the program in basic rom space?

View File

@ -4,30 +4,39 @@
main {
romsub @bank 0 $4000 = routine(uword argument @AY) -> ubyte @A
romsub @bank 15 $ffd2 = character_out(ubyte char @A)
romsub @bank %100 $f800 = routine_in_kernal_addr_space(uword arg @AY) -> uword @AY
; ^-- I/O enabled, basic and kernal roms banked out
sub start() {
sys.memcopy(&the_routine, $4000, 255)
; copy the routine into kernal area address space
sys.memcopy(&the_invert_routine, $f800, 255)
cx16.r0L = routine($2233)
txt.print("result=")
txt.print_ub(cx16.r0L)
cx16.r0 = the_invert_routine(12345)
txt.print("inverted (normal)=")
txt.print_uw(cx16.r0)
txt.nl()
cx16.r0L = routine($3344)
txt.print("result=")
txt.print_ub(cx16.r0L)
cx16.r0 = routine_in_kernal_addr_space(12345)
txt.print("inverted (kernal space)=")
txt.print_uw(cx16.r0)
txt.nl()
txt.print("inverted (callfar)=")
cx16.r0=callfar(%100, $f800, 12345)
txt.print_uw(cx16.r0)
txt.nl()
txt.print("inverted (callfar2)=")
cx16.r0=callfar2(%100, $f800, 57, 0, 48, false)
txt.print_uw(cx16.r0)
txt.nl()
character_out('!')
character_out('\n')
}
asmsub the_routine(uword arg @AY) -> ubyte @A {
asmsub the_invert_routine(uword arg @AY) -> uword @AY {
%asm {{
sty P8ZP_SCRATCH_REG
clc
adc P8ZP_SCRATCH_REG
eor #$ff
pha
tya
eor #$ff
tay
pla
rts
}}
}