romsub @bank now also accepts a variable so the bank can be dynamic

This commit is contained in:
Irmen de Jong 2024-11-05 23:56:58 +01:00
parent 491e5dbcfb
commit 77e376f6bf
24 changed files with 159 additions and 53 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: Pair<UByte?, UInt>?, // null in case of asmsub, specified in case of romsub. bank, address.
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

@ -92,8 +92,10 @@ fun printAst(root: PtNode, skipLibraries: Boolean, output: (text: String) -> Uni
if(node.address == null) {
str + "asmsub ${node.name}($params) $clobbers $returns"
} else {
val bank = if(node.address.first!=null) "@bank ${node.address.first}" else ""
str + "romsub $bank ${node.address.second.toHex()} = ${node.name}($params) $clobbers $returns"
val bank = if(node.address.constbank!=null) "@bank ${node.address.constbank}"
else if(node.address.varbank!=null) "@bank ${node.address.varbank?.name}"
else ""
str + "romsub $bank ${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: Pair<UByte?, UInt>?, // bank, address
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 constbank: UByte?, var varbank: PtIdentifier?, val address: UInt)
}
class PtSub(

View File

@ -48,6 +48,9 @@ class AsmGen6502(val prefixSymbols: Boolean): ICodeGeneratorBackend {
is PtAsmSub -> {
prefixNamedNode(node)
node.parameters.forEach { (_, param) -> prefixNamedNode(param) }
if(node.address?.varbank!=null) {
node.address!!.varbank = node.address!!.varbank!!.prefix(node, st)
}
}
is PtSub -> {
prefixNamedNode(node)

View File

@ -40,30 +40,78 @@ 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 {
val bank = sub.address?.first
if(bank==null)
asmgen.out(" jsr $subAsmName")
val bank = sub.address?.constbank?.toString()
if(bank==null) {
val varbank = if(sub.address?.varbank==null) null else asmgen.asmVariableName(sub.address!!.varbank!!)
if(varbank!=null) {
when(asmgen.options.compTarget.name) {
"cx16" -> {
// JSRFAR can jump to a banked RAM address as well!
asmgen.out("""
php
pha
lda $varbank
sta +
pla
plp
jsr cx16.JSRFAR
.word $subAsmName ; ${sub.address!!.address.toHex()}
+ .byte 0 ; modified"""
)
}
"c64" -> {
asmgen.out("""
php
pha
lda $varbank
sta +
pla
plp
jsr c64.x16jsrfar
.word $subAsmName ; ${sub.address!!.address.toHex()}
+ .byte 0 ; modified"""
)
}
"c128" -> {
asmgen.out("""
php
pha
lda $varbank
sta +
pla
plp
jsr c128.x16jsrfar
.word $subAsmName ; ${sub.address!!.address.toHex()}
+ .byte 0 ; modified"""
)
}
else -> throw AssemblyError("callfar is not supported on the selected compilation target")
}
} else {
asmgen.out(" jsr $subAsmName")
}
}
else {
when(asmgen.options.compTarget.name) {
"cx16" -> {
// JSRFAR can jump to a banked RAM address as well!
asmgen.out("""
jsr cx16.JSRFAR
.word $subAsmName ; ${sub.address!!.second.toHex()}
.word $subAsmName ; ${sub.address!!.address.toHex()}
.byte $bank"""
)
}
"c64" -> {
asmgen.out("""
jsr c64.x16jsrfar
.word $subAsmName ; ${sub.address!!.second.toHex()}
.word $subAsmName ; ${sub.address!!.address.toHex()}
.byte $bank"""
)
}
"c128" -> {
asmgen.out("""
jsr c128.x16jsrfar
.word $subAsmName ; ${sub.address!!.second.toHex()}
.word $subAsmName ; ${sub.address!!.address.toHex()}
.byte $bank"""
)
}

View File

@ -783,8 +783,10 @@ internal class ProgramAndVarsGen(
.forEach { asmsub ->
asmsub as PtAsmSub
val address = asmsub.address!!
val bank = if(address.first!=null) "; @bank ${address.first}" else ""
asmgen.out(" ${asmsub.name} = ${address.second.toHex()} $bank")
val bank = if(address.constbank!=null) "; @bank ${address.constbank}"
else if(address.varbank!=null) "; @bank ${address.varbank?.name}"
else ""
asmgen.out(" ${asmsub.name} = ${address.address.toHex()} $bank")
}
}

View File

@ -645,14 +645,14 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
IRInstruction(Opcode.CALL, labelSymbol = fcall.name, fcallArgs = FunctionCallArgs(argRegisters, returnRegs))
else {
val address = callTarget.address!!
if(address.first==null) {
if(address.constbank==null && address.varbank==null) {
IRInstruction(
Opcode.CALL,
address = address.second.toInt(),
address = address.address.toInt(),
fcallArgs = FunctionCallArgs(argRegisters, returnRegs))
}
else {
TODO("callfar is not implemented for the selected compilation target")
TODO("callfar into another bank is not implemented for the selected compilation target")
}
}
addInstr(result, call, null)
@ -767,10 +767,10 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
IRInstruction(Opcode.CALL, labelSymbol = fcall.name, fcallArgs = FunctionCallArgs(argRegisters, returnRegisters))
else {
val address = callTarget.address!!
if(address.first==null) {
if(address.constbank==null && address.varbank==null) {
IRInstruction(
Opcode.CALL,
address = address.second.toInt(),
address = address.address.toInt(),
fcallArgs = FunctionCallArgs(argRegisters, returnRegisters)
)
}

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", null to 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

@ -377,9 +377,16 @@ internal class AstChecker(private val program: Program,
if(uniqueNames.size!=subroutine.parameters.size)
err("parameter names must be unique")
val bank = subroutine.asmAddress?.first
if(bank!=null && bank>255u)
err("bank must be 0 to 255")
val bank = subroutine.asmAddress?.constbank
if (bank!=null) {
if (bank > 255u) err("bank must be 0 to 255")
if (subroutine.asmAddress?.varbank!=null) throw FatalAstException("need either constant or variable bank")
}
val varbank = subroutine.asmAddress?.varbank
if(varbank!=null) {
if(varbank.targetVarDecl(program)?.datatype!=DataType.UBYTE)
err("bank variable must be ubyte")
}
if(subroutine.inline && subroutine.asmAddress!=null)
throw FatalAstException("romsub cannot be inline")

View File

@ -466,7 +466,8 @@ 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 srcSub.asmAddress!!.first to srcSub.asmAddress!!.second
val varbank = if(srcSub.asmAddress?.varbank==null) null else transform(srcSub.asmAddress!!.varbank!!)
val asmAddr = if(srcSub.asmAddress==null) null else PtAsmSub.Address(srcSub.asmAddress!!.constbank, varbank, srcSub.asmAddress!!.address)
val sub = PtAsmSub(srcSub.name,
asmAddr,
srcSub.asmClobbers,
@ -475,6 +476,8 @@ class IntermediateAstMaker(private val program: Program, private val errors: IEr
srcSub.inline,
srcSub.position)
sub.parameters.forEach { it.second.parent=sub }
if(varbank!=null)
asmAddr?.varbank?.parent = sub
if(srcSub.asmAddress==null) {
var combinedTrueAsm = ""

View File

@ -181,8 +181,10 @@ class AstToSourceTextConverter(val output: (text: String) -> Unit, val program:
output("inline ")
if(subroutine.isAsmSubroutine) {
if(subroutine.asmAddress!=null) {
val bank = if(subroutine.asmAddress.first!=null) "@bank ${subroutine.asmAddress.first}" else ""
output("romsub $bank ${subroutine.asmAddress.second.toHex()} = ${subroutine.name} (")
val bank = if(subroutine.asmAddress.constbank!=null) "@bank ${subroutine.asmAddress.constbank}"
else if(subroutine.asmAddress.varbank!=null) "@bank ${subroutine.asmAddress.varbank?.nameInSource?.joinToString(".")}"
else ""
output("romsub $bank ${subroutine.asmAddress.address.toHex()} = ${subroutine.name} (")
}
else
output("asmsub ${subroutine.name} (")

View File

@ -173,8 +173,10 @@ private class SymbolDumper(val skipLibraries: Boolean): IAstVisitor {
}
}
if(subroutine.asmAddress!=null) {
val bank = if(subroutine.asmAddress.first!=null) "@bank ${subroutine.asmAddress.first}" else ""
output("$bank = ${subroutine.asmAddress.second.toHex()}")
val bank = if(subroutine.asmAddress.constbank!=null) "@bank ${subroutine.asmAddress.constbank}"
else if(subroutine.asmAddress.varbank!=null) "@bank ${subroutine.asmAddress.varbank?.nameInSource?.joinToString(".")}"
else ""
output("$bank = ${subroutine.asmAddress.address.toHex()}")
}
output("\n")

View File

@ -177,11 +177,13 @@ private fun AsmsubroutineContext.toAst(): Subroutine {
private fun RomsubroutineContext.toAst(): Subroutine {
val subdecl = asmsub_decl().toAst()
val bank = bank?.toAst()?.number?.toUInt()?.toUByte()
val constbank = constbank?.toAst()?.number?.toUInt()?.toUByte()
val varbank = varbank?.toAst()
val addr = address.toAst().number.toUInt()
val address = Subroutine.Address(constbank, varbank, addr)
return Subroutine(subdecl.name, subdecl.parameters.toMutableList(), subdecl.returntypes.toMutableList(),
subdecl.asmParameterRegisters, subdecl.asmReturnvaluesRegisters,
subdecl.asmClobbers, bank to addr, true, inline = false, statements = mutableListOf(), position = toPosition()
subdecl.asmClobbers, address, true, inline = false, statements = mutableListOf(), position = toPosition()
)
}

View File

@ -442,7 +442,7 @@ data class AddressOf(var identifier: IdentifierReference, var arrayIndex: ArrayI
}
val targetAsmAddress = (target as? Subroutine)?.asmAddress
if(targetAsmAddress!=null) {
return NumericLiteral(DataType.UWORD, targetAsmAddress.second.toDouble(), position)
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: Pair<UByte?, UInt>?, // bank, address
val asmAddress: Address?,
val isAsmSubroutine: Boolean,
var inline: Boolean,
var hasBeenInlined: Boolean=false,
@ -818,6 +818,7 @@ class Subroutine(override val name: String,
override fun linkParents(parent: Node) {
this.parent = parent
this.asmAddress?.varbank?.linkParents(this)
parameters.forEach { it.linkParents(this) }
statements.forEach { it.linkParents(this) }
}
@ -834,11 +835,18 @@ class Subroutine(override val name: String,
statements[idx] = replacement
replacement.parent = this
}
is NumericLiteral -> {
if(node===asmAddress?.varbank) {
asmAddress.constbank = replacement.number.toInt().toUByte()
asmAddress.varbank = null
} else throw FatalAstException("can't replace")
}
else -> throw FatalAstException("can't replace")
}
}
override fun referencesIdentifier(nameInSource: List<String>): Boolean =
asmAddress?.varbank?.referencesIdentifier(nameInSource)==true ||
statements.any { it.referencesIdentifier(nameInSource) } ||
parameters.any { it.referencesIdentifier(nameInSource) }
@ -846,6 +854,17 @@ 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(var constbank: UByte?, var varbank: IdentifierReference?, val address: UInt) {
override fun toString(): String {
if(constbank!=null)
return "$constbank:${address.toHex()}"
else if(varbank!=null)
return "${varbank?.nameInSource?.joinToString(".")}:${address.toHex()}"
else
return address.toHex()
}
}
}
open class SubroutineParameter(val name: String,

View File

@ -276,6 +276,7 @@ abstract class AstWalker {
fun visit(subroutine: Subroutine, parent: Node) {
track(before(subroutine, parent), subroutine, parent)
subroutine.asmAddress?.varbank?.accept(this, subroutine)
subroutine.statements.forEach { it.accept(this, subroutine) }
track(after(subroutine, parent), subroutine, parent)
}

View File

@ -45,6 +45,7 @@ interface IAstVisitor {
}
fun visit(subroutine: Subroutine) {
subroutine.asmAddress?.varbank?.accept(this)
subroutine.statements.forEach { it.accept(this) }
}

View File

@ -81,7 +81,7 @@ Foreign function interface (external/ROM calls)
-----------------------------------------------
- You can use the ``romsub`` keyword to define the call signature of foreign functions (usually ROM routines, hence the name) in a natural way.
Calling those generates code that is as efficient or even more efficient as calling regular subroutines.
No additional stubs are needed. (unless there is bank switching going on, but this *may* be improved in a future language version)
No additional stubs are needed. You can even specify the memory bank the routine is in and the compiler takes care of bank switching when calling it.
Optimizations
-------------

View File

@ -794,7 +794,7 @@ and returning stuff in several registers as well. The ``clobbers`` clause is use
what CPU registers are clobbered by the call instead of being unchanged or returning a meaningful result value.
**Banks:** it is possible to declare a non-standard ROM or RAM bank that the routine is living in, with ``@bank`` like this:
``romsub @bank 10 $C09F = audio_init()`` to define a routine at $C09F in bank 10.
``romsub @bank 10 $C09F = audio_init()`` to define a routine at $C09F in bank 10. You can also specify a variable for the bank.
See :ref:`banking` for more information.
.. note::

View File

@ -51,9 +51,11 @@ The compiler will then transparently change a call to this routine so that the c
automatically before the normal jump to the subroutine (and switched back on return). The programmer doesn't
have to bother anymore with setting/resetting the banks manually, or having the program crash because
the routine is called in the wrong bank! You define such a routine by adding ``@bank <bank>``
to the romsub subroutine definition. This specifies the bank number where the subroutine is located in::
to the romsub subroutine definition. This specifies the bank number where the subroutine is located in.
You can use a constant bank number 0-255, or a ubyte variable to make it dynamic::
romsub @bank 10 $C09F = audio_init()
romsub @bank banknr $A000 = first_hiram_routine()
When you then call this routine in your program as usual, the compiler will no longer generate a simple JSR instruction to the
routine. Instead it will generate a piece of code that automatically switches the ROM or RAM bank to the
@ -64,7 +66,7 @@ a similar call exists (but requires a lot more code to prepare, so beware).
On the Commodore 64 some custom code is also emitted that toggle the banks, retains some registers, and does the call.
Other compilation targets don't have banking or prog8 doesn't yet support automatic bank selection on them.
There's a "banking" (not financial) example for the Commander X16 that shows a possible application
There's a "banking" example for the Commander X16 that shows a possible application
of the romsub with bank support, check out the `bank example code <https://github.com/irmen/prog8/tree/master/examples/cx16/banking>`_ .

View File

@ -1,8 +1,6 @@
TODO
====
make @bank accept a variable as well to make it dynamic
rename 'romsub' to 'extsub' ? keep romsub as alias?
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.

View File

@ -12,6 +12,8 @@ main {
sub start() {
; load the example libraries in hiram banks 4 and 5
; in this example these are constants, but you can also specify
; a variable for the bank so you can vary the bank where the routine is loaded.
cx16.rambank(4)
void diskio.load("library1.prg", $a000)
cx16.rambank(5)

View File

@ -1,23 +1,31 @@
%import textio
%zeropage basicsafe
%option no_sysinit
main {
sub start() {
basic_area.routine1()
}
}
ubyte bank
basic_area $a000 {
sub routine1() {
txt.print("hello from basic rom area ")
txt.print_uwhex(&routine1, true)
romsub @bank bank $a000 = routine_in_hiram(uword arg @AY) -> uword @AY
sub start() {
; copy the routine into hiram bank 8
bank = 8
cx16.rambank(bank)
sys.memcopy(&the_increment_routine, $a000, 255)
cx16.rambank(1)
txt.print("incremented by one=")
txt.print_uw(routine_in_hiram(37119))
txt.nl()
}
}
;hiram_area $c000 {
; %option force_output
; sub thing() {
; cx16.r0++
; }
;}
asmsub the_increment_routine(uword arg @AY) -> uword @AY {
%asm {{
clc
adc #1
bcc +
iny
+ rts
}}
}
}

View File

@ -294,7 +294,7 @@ asmsubroutine :
;
romsubroutine :
'romsub' ('@bank' bank=integerliteral)? address=integerliteral '=' asmsub_decl
'romsub' ('@bank' (constbank=integerliteral | varbank=scoped_identifier))? address=integerliteral '=' asmsub_decl
;
asmsub_decl : identifier '(' asmsub_params? ')' asmsub_clobbers? asmsub_returns? ;