added @rombank and @rambank bank number tags on romsubs

on cx16 and c128 targets the compiler then automatically inserts a CALLFAR instead of a regular JSR to automatically do the bank switching.
This commit is contained in:
Irmen de Jong 2024-11-03 17:44:37 +01:00
parent 178e60bba0
commit 155896c4c7
17 changed files with 132 additions and 34 deletions

View File

@ -1,5 +1,6 @@
package prog8.code
import prog8.code.ast.PtAsmSub
import prog8.code.ast.PtNode
import prog8.code.ast.PtProgram
import prog8.code.core.*
@ -257,7 +258,7 @@ class StSub(name: String, val parameters: List<StSubroutineParameter>, val retur
class StRomSub(name: String,
val address: UInt?, // null in case of asmsub, specified in case of romsub
val address: PtAsmSub.Address?, // null in case of asmsub, specified in case of romsub
val parameters: List<StRomSubParameter>,
val returns: List<StRomSubParameter>,
astNode: PtNode) :

View File

@ -89,10 +89,12 @@ fun printAst(root: PtNode, skipLibraries: Boolean, output: (text: String) -> Uni
}"
}
val str = if (node.inline) "inline " else ""
if(node.address==null) {
if(node.address == null) {
str + "asmsub ${node.name}($params) $clobbers $returns"
} else {
str + "romsub ${node.address.toHex()} = ${node.name}($params) $clobbers $returns"
val rombank = if(node.address.rombank!=null) "@rombank ${node.address.rombank}" else ""
val rambank = if(node.address.rambank!=null) "@rambank ${node.address.rambank}" else ""
str + "romsub $rombank $rambank ${node.address.address.toHex()} = ${node.name}($params) $clobbers $returns"
}
}
is PtBlock -> {

View File

@ -10,13 +10,16 @@ sealed interface IPtSubroutine {
class PtAsmSub(
name: String,
val address: UInt?,
val address: Address?,
val clobbers: Set<CpuRegister>,
val parameters: List<Pair<RegisterOrStatusflag, PtSubroutineParameter>>,
val returns: List<Pair<RegisterOrStatusflag, DataType>>,
val inline: Boolean,
position: Position
) : PtNamedNode(name, position), IPtSubroutine
) : PtNamedNode(name, position), IPtSubroutine {
class Address(val rombank: UByte?, val rambank: UByte?, val address: UInt)
}
class PtSub(

View File

@ -40,7 +40,55 @@ internal class FunctionCallAsmGen(private val program: PtProgram, private val as
sub.children.forEach { asmgen.translate(it as PtInlineAssembly) }
asmgen.out(" \t; inlined routine end: ${sub.name}")
} else {
asmgen.out(" jsr $subAsmName")
val rombank = sub.address?.rombank
val rambank = sub.address?.rambank
if(rombank==null && rambank==null)
asmgen.out(" jsr $subAsmName")
else {
when(asmgen.options.compTarget.name) {
"cx16" -> {
if(rambank!=null) {
// JSRFAR can jump to a banked RAM address as well!
asmgen.out("""
jsr cx16.JSRFAR
.word $subAsmName ; ${sub.address!!.address.toHex()}
.byte $rambank"""
)
} else {
asmgen.out("""
jsr cx16.JSRFAR
.word $subAsmName ; ${sub.address!!.address.toHex()}
.byte $rombank"""
)
}
}
"c128" -> {
val bank = rambank ?: rombank!!
// see https://cx16.dk/c128-kernal-routines/jsrfar.html
asmgen.out("""
sty $08
stx $07
sta $06
php
pla
sta $05
lda #$bank
ldy #>$subAsmName
ldx #<$subAsmName
sta $02
sty $03
stx $04
jsr c128.JSRFAR
lda $05
pha
lda $06
ldx $07
ldy $08
plp""")
}
else -> throw AssemblyError("callfar is not supported on the selected compilation target")
}
}
}
}
else if(sub is PtSub) {

View File

@ -780,7 +780,9 @@ internal class ProgramAndVarsGen(
.filter { it is PtAsmSub && it.address!=null }
.forEach { asmsub ->
asmsub as PtAsmSub
asmgen.out(" ${asmsub.name} = ${asmsub.address!!.toHex()}")
val address = asmsub.address!!
val bank = if(address.rombank!=null) "; @rombank ${address.rombank}" else if(address.rambank!=null) "; @rambank ${address.rambank}" else ""
asmgen.out(" ${asmsub.name} = ${address.address.toHex()} $bank")
}
}

View File

@ -643,8 +643,18 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
val call =
if(callTarget.address==null)
IRInstruction(Opcode.CALL, labelSymbol = fcall.name, fcallArgs = FunctionCallArgs(argRegisters, returnRegs))
else
IRInstruction(Opcode.CALL, address = callTarget.address!!.toInt(), fcallArgs = FunctionCallArgs(argRegisters, returnRegs))
else {
val address = callTarget.address!!
if(address.rombank==null && address.rambank==null) {
IRInstruction(
Opcode.CALL,
address = address.address.toInt(),
fcallArgs = FunctionCallArgs(argRegisters, returnRegs))
}
else {
TODO("callfar is not implemented for the selected compilation target")
}
}
addInstr(result, call, null)
var finalReturnRegister = returnRegSpec?.registerNum ?: -1
@ -755,8 +765,17 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
val call =
if(callTarget.address==null)
IRInstruction(Opcode.CALL, labelSymbol = fcall.name, fcallArgs = FunctionCallArgs(argRegisters, returnRegisters))
else
IRInstruction(Opcode.CALL, address = callTarget.address!!.toInt(), fcallArgs = FunctionCallArgs(argRegisters, returnRegisters))
else {
val address = callTarget.address!!
if(address.rombank==null && address.rambank==null) {
IRInstruction(
Opcode.CALL,
address = address.address.toInt(),
fcallArgs = FunctionCallArgs(argRegisters, returnRegisters)
)
}
else TODO("romsub with banked address got called ${callTarget.name}")
}
addInstr(result, call, null)
val resultRegs = returnRegisters.filter{it.dt!=IRDataType.FLOAT}.map{it.registerNum}
val resultFpRegs = returnRegisters.filter{it.dt==IRDataType.FLOAT}.map{it.registerNum}

View File

@ -534,7 +534,7 @@ class TestVmCodeGen: FunSpec({
val codegen = VmCodeGen()
val program = PtProgram("test", DummyMemsizer, DummyStringEncoder)
val block = PtBlock("main", false, SourceCode.Generated("test"), PtBlock.Options(), Position.DUMMY)
val romsub = PtAsmSub("routine", 0x5000u, setOf(CpuRegister.Y), emptyList(), emptyList(), false, Position.DUMMY)
val romsub = PtAsmSub("routine", PtAsmSub.Address(null, null, 0x5000u), setOf(CpuRegister.Y), emptyList(), emptyList(), false, Position.DUMMY)
block.add(romsub)
val sub = PtSub("start", emptyList(), null, Position.DUMMY)
val call = PtFunctionCall("main.routine", true, DataType.UNDEFINED, Position.DUMMY)

View File

@ -59,8 +59,6 @@ cbm {
romsub $FA65 = IRQDFRT() clobbers(A,X,Y) ; default IRQ routine
romsub $FF33 = IRQDFEND() clobbers(A,X,Y) ; default IRQ end/cleanup
; TODO c128 a bunch of kernal routines are missing here that are specific to the c128
romsub $FF81 = CINT() clobbers(A,X,Y) ; (alias: SCINIT) initialize screen editor and video chip
romsub $FF84 = IOINIT() clobbers(A, X) ; initialize I/O devices (CIA, SID, IRQ)
romsub $FF87 = RAMTAS() clobbers(A,X,Y) ; initialize RAM, tape buffer, screen
@ -319,6 +317,11 @@ c128 {
&ubyte VM4 = $0A2F ; starting page for VDC attribute mem
; TODO c128 a bunch of kernal routines are missing here that are specific to the c128
romsub $FF6E = JSRFAR()
; ---- C128 specific system utility routines: ----
asmsub disable_basic() clobbers(A) {

View File

@ -377,6 +377,15 @@ internal class AstChecker(private val program: Program,
if(uniqueNames.size!=subroutine.parameters.size)
err("parameter names must be unique")
val rambank = subroutine.asmAddress?.rambank
val rombank = subroutine.asmAddress?.rombank
if(rambank!=null && rambank>255u)
err("bank must be 0 to 255")
if(rombank!=null && rombank>255u)
err("bank must be 0 to 255")
if(subroutine.inline && subroutine.asmAddress!=null)
throw FatalAstException("romsub cannot be inline")
super.visit(subroutine)
// user-defined subroutines can only have zero or one return type

View File

@ -466,8 +466,9 @@ class IntermediateAstMaker(private val program: Program, private val errors: IEr
private fun transformAsmSub(srcSub: Subroutine): PtAsmSub {
val params = srcSub.asmParameterRegisters.zip(srcSub.parameters.map { PtSubroutineParameter(it.name, it.type, it.position) })
val asmAddr = if(srcSub.asmAddress==null) null else PtAsmSub.Address(srcSub.asmAddress!!.rombank?.toUByte(), srcSub.asmAddress!!.rambank?.toUByte(), srcSub.asmAddress!!.address)
val sub = PtAsmSub(srcSub.name,
srcSub.asmAddress,
asmAddr,
srcSub.asmClobbers,
params,
srcSub.asmReturnvaluesRegisters.zip(srcSub.returntypes),

View File

@ -181,7 +181,9 @@ class AstToSourceTextConverter(val output: (text: String) -> Unit, val program:
output("inline ")
if(subroutine.isAsmSubroutine) {
if(subroutine.asmAddress!=null) {
output("romsub ${subroutine.asmAddress.toHex()} = ${subroutine.name} (")
val rombank = if(subroutine.asmAddress.rombank!=null) "@rombank ${subroutine.asmAddress.rombank}" else ""
val rambank = if(subroutine.asmAddress.rambank!=null) "@rambank ${subroutine.asmAddress.rambank}" else ""
output("romsub $rombank $rambank ${subroutine.asmAddress.address.toHex()} = ${subroutine.name} (")
}
else
output("asmsub ${subroutine.name} (")

View File

@ -172,8 +172,11 @@ private class SymbolDumper(val skipLibraries: Boolean): IAstVisitor {
output("-> $rts ")
}
}
if(subroutine.asmAddress!=null)
output("= ${subroutine.asmAddress.toHex()}")
if(subroutine.asmAddress!=null) {
val rombank = if(subroutine.asmAddress.rombank!=null) "@rombank ${subroutine.asmAddress.rombank}" else ""
val rambank = if(subroutine.asmAddress.rambank!=null) "@rambank ${subroutine.asmAddress.rambank}" else ""
output("$rombank $rambank = ${subroutine.asmAddress.address.toHex()}")
}
output("\n")
}

View File

@ -177,7 +177,10 @@ private fun AsmsubroutineContext.toAst(): Subroutine {
private fun RomsubroutineContext.toAst(): Subroutine {
val subdecl = asmsub_decl().toAst()
val address = integerliteral().toAst().number.toUInt()
val rombank = rombankspec()?.integerliteral()?.toAst()?.number?.toUInt()
val rambank = rambankspec()?.integerliteral()?.toAst()?.number?.toUInt()
val addr = integerliteral().toAst().number.toUInt()
val address = Subroutine.Address(rombank, rambank, addr)
return Subroutine(subdecl.name, subdecl.parameters.toMutableList(), subdecl.returntypes.toMutableList(),
subdecl.asmParameterRegisters, subdecl.asmReturnvaluesRegisters,
subdecl.asmClobbers, address, true, inline = false, statements = mutableListOf(), position = toPosition()

View File

@ -440,9 +440,10 @@ data class AddressOf(var identifier: IdentifierReference, var arrayIndex: ArrayI
}
}
}
val targetSub = target as? Subroutine
if(targetSub?.asmAddress!=null) {
return NumericLiteral(DataType.UWORD, targetSub.asmAddress.toDouble(), position)
val targetAsmAddress = (target as? Subroutine)?.asmAddress
if(targetAsmAddress!=null) {
if(targetAsmAddress.rombank==null && targetAsmAddress.rambank==null)
return NumericLiteral(DataType.UWORD, targetAsmAddress.address.toDouble(), position)
}
return null
}

View File

@ -806,7 +806,7 @@ class Subroutine(override val name: String,
val asmParameterRegisters: List<RegisterOrStatusflag>,
val asmReturnvaluesRegisters: List<RegisterOrStatusflag>,
val asmClobbers: Set<CpuRegister>,
val asmAddress: UInt?,
val asmAddress: Address?,
val isAsmSubroutine: Boolean,
var inline: Boolean,
var hasBeenInlined: Boolean=false,
@ -846,6 +846,8 @@ class Subroutine(override val name: String,
override fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
override fun toString() =
"Subroutine(name=$name, parameters=$parameters, returntypes=$returntypes, ${statements.size} statements, address=$asmAddress)"
class Address(val rombank: UInt?, val rambank: UInt?, val address: UInt)
}
open class SubroutineParameter(val name: String,

View File

@ -1,6 +1,12 @@
TODO
====
get rid of audio.p8 module , rewrite audio romsubs , rewrite audio example to use syslib routines
add docs for @rombank @rambank on romsubs. Add promo in docs that prog8 does automatic bank switching when calling such a romsub (on cx16 and c128)
rename 'romsub' to 'extsub' ?
for releasenotes: gfx2.width and gfx2.height got renamed as gfx_lores.WIDTH/HEIGHT or gfx_hires4.WIDTH/HEIGTH constants. Screen mode routines also renamed.
regenerate symbol dump files
@ -83,13 +89,3 @@ STRUCTS?
- ARRAY remains the type for an array literal (so we can keep doing register-indexed addressing directly on it)
- we probably need to have a STRBYREF and ARRAYBYREF if we deal with a pointer to a string / array (such as when passing it to a function)
the subtype of those should include the declared element type and the declared length of the string / array
Other language/syntax features to think about
---------------------------------------------
- add (rom/ram)bank support to romsub. A call will then automatically switch banks, use callfar and something else when in banked ram.
challenges: how to not make this too X16 specific? How does the compiler know what bank to switch (ram/rom)?
How to make it performant when we want to (i.e. NOT have it use callfar/auto bank switching) ?
Maybe by having a %option rombank=4 rambank=22 to set that as fixed rombank/rambank for that subroutine/block (and pray the user doesn't change it themselves)
and then only do bank switching if the bank of the routine is different from the configured rombank/rambank.

View File

@ -294,9 +294,12 @@ asmsubroutine :
;
romsubroutine :
'romsub' integerliteral '=' asmsub_decl
'romsub' (rombankspec | rambankspec)? integerliteral '=' asmsub_decl
;
rombankspec : '@rombank' integerliteral;
rambankspec : '@rambank' integerliteral;
asmsub_decl : identifier '(' asmsub_params? ')' asmsub_clobbers? asmsub_returns? ;
asmsub_params : asmsub_param (',' EOL? asmsub_param)* ;