mirror of
https://github.com/irmen/prog8.git
synced 2025-01-12 19:29:50 +00:00
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:
parent
178e60bba0
commit
155896c4c7
@ -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) :
|
||||
|
@ -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 -> {
|
||||
|
@ -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(
|
||||
|
@ -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) {
|
||||
|
@ -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")
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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}
|
||||
|
@ -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)
|
||||
|
@ -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) {
|
||||
|
@ -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
|
||||
|
@ -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),
|
||||
|
@ -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} (")
|
||||
|
@ -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")
|
||||
}
|
||||
|
@ -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()
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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,
|
||||
|
@ -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.
|
||||
|
@ -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)* ;
|
||||
|
Loading…
x
Reference in New Issue
Block a user