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 package prog8.code
import prog8.code.ast.PtAsmSub
import prog8.code.ast.PtNode import prog8.code.ast.PtNode
import prog8.code.ast.PtProgram import prog8.code.ast.PtProgram
import prog8.code.core.* import prog8.code.core.*
@ -257,7 +258,7 @@ class StSub(name: String, val parameters: List<StSubroutineParameter>, val retur
class StRomSub(name: String, 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 parameters: List<StRomSubParameter>,
val returns: List<StRomSubParameter>, val returns: List<StRomSubParameter>,
astNode: PtNode) : astNode: PtNode) :

View File

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

View File

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

View File

@ -48,6 +48,9 @@ class AsmGen6502(val prefixSymbols: Boolean): ICodeGeneratorBackend {
is PtAsmSub -> { is PtAsmSub -> {
prefixNamedNode(node) prefixNamedNode(node)
node.parameters.forEach { (_, param) -> prefixNamedNode(param) } node.parameters.forEach { (_, param) -> prefixNamedNode(param) }
if(node.address?.varbank!=null) {
node.address!!.varbank = node.address!!.varbank!!.prefix(node, st)
}
} }
is PtSub -> { is PtSub -> {
prefixNamedNode(node) 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) } sub.children.forEach { asmgen.translate(it as PtInlineAssembly) }
asmgen.out(" \t; inlined routine end: ${sub.name}") asmgen.out(" \t; inlined routine end: ${sub.name}")
} else { } else {
val bank = sub.address?.first val bank = sub.address?.constbank?.toString()
if(bank==null) if(bank==null) {
asmgen.out(" jsr $subAsmName") 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 { else {
when(asmgen.options.compTarget.name) { when(asmgen.options.compTarget.name) {
"cx16" -> { "cx16" -> {
// JSRFAR can jump to a banked RAM address as well! // JSRFAR can jump to a banked RAM address as well!
asmgen.out(""" asmgen.out("""
jsr cx16.JSRFAR jsr cx16.JSRFAR
.word $subAsmName ; ${sub.address!!.second.toHex()} .word $subAsmName ; ${sub.address!!.address.toHex()}
.byte $bank""" .byte $bank"""
) )
} }
"c64" -> { "c64" -> {
asmgen.out(""" asmgen.out("""
jsr c64.x16jsrfar jsr c64.x16jsrfar
.word $subAsmName ; ${sub.address!!.second.toHex()} .word $subAsmName ; ${sub.address!!.address.toHex()}
.byte $bank""" .byte $bank"""
) )
} }
"c128" -> { "c128" -> {
asmgen.out(""" asmgen.out("""
jsr c128.x16jsrfar jsr c128.x16jsrfar
.word $subAsmName ; ${sub.address!!.second.toHex()} .word $subAsmName ; ${sub.address!!.address.toHex()}
.byte $bank""" .byte $bank"""
) )
} }

View File

@ -783,8 +783,10 @@ internal class ProgramAndVarsGen(
.forEach { asmsub -> .forEach { asmsub ->
asmsub as PtAsmSub asmsub as PtAsmSub
val address = asmsub.address!! val address = asmsub.address!!
val bank = if(address.first!=null) "; @bank ${address.first}" else "" val bank = if(address.constbank!=null) "; @bank ${address.constbank}"
asmgen.out(" ${asmsub.name} = ${address.second.toHex()} $bank") 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)) IRInstruction(Opcode.CALL, labelSymbol = fcall.name, fcallArgs = FunctionCallArgs(argRegisters, returnRegs))
else { else {
val address = callTarget.address!! val address = callTarget.address!!
if(address.first==null) { if(address.constbank==null && address.varbank==null) {
IRInstruction( IRInstruction(
Opcode.CALL, Opcode.CALL,
address = address.second.toInt(), address = address.address.toInt(),
fcallArgs = FunctionCallArgs(argRegisters, returnRegs)) fcallArgs = FunctionCallArgs(argRegisters, returnRegs))
} }
else { 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) 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)) IRInstruction(Opcode.CALL, labelSymbol = fcall.name, fcallArgs = FunctionCallArgs(argRegisters, returnRegisters))
else { else {
val address = callTarget.address!! val address = callTarget.address!!
if(address.first==null) { if(address.constbank==null && address.varbank==null) {
IRInstruction( IRInstruction(
Opcode.CALL, Opcode.CALL,
address = address.second.toInt(), address = address.address.toInt(),
fcallArgs = FunctionCallArgs(argRegisters, returnRegisters) fcallArgs = FunctionCallArgs(argRegisters, returnRegisters)
) )
} }

View File

@ -534,7 +534,7 @@ class TestVmCodeGen: FunSpec({
val codegen = VmCodeGen() val codegen = VmCodeGen()
val program = PtProgram("test", DummyMemsizer, DummyStringEncoder) val program = PtProgram("test", DummyMemsizer, DummyStringEncoder)
val block = PtBlock("main", false, SourceCode.Generated("test"), PtBlock.Options(), Position.DUMMY) 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) block.add(romsub)
val sub = PtSub("start", emptyList(), null, Position.DUMMY) val sub = PtSub("start", emptyList(), null, Position.DUMMY)
val call = PtFunctionCall("main.routine", true, DataType.UNDEFINED, 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) if(uniqueNames.size!=subroutine.parameters.size)
err("parameter names must be unique") err("parameter names must be unique")
val bank = subroutine.asmAddress?.first val bank = subroutine.asmAddress?.constbank
if(bank!=null && bank>255u) if (bank!=null) {
err("bank must be 0 to 255") 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) if(subroutine.inline && subroutine.asmAddress!=null)
throw FatalAstException("romsub cannot be inline") 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 { private fun transformAsmSub(srcSub: Subroutine): PtAsmSub {
val params = srcSub.asmParameterRegisters.zip(srcSub.parameters.map { PtSubroutineParameter(it.name, it.type, it.position) }) 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, val sub = PtAsmSub(srcSub.name,
asmAddr, asmAddr,
srcSub.asmClobbers, srcSub.asmClobbers,
@ -475,6 +476,8 @@ class IntermediateAstMaker(private val program: Program, private val errors: IEr
srcSub.inline, srcSub.inline,
srcSub.position) srcSub.position)
sub.parameters.forEach { it.second.parent=sub } sub.parameters.forEach { it.second.parent=sub }
if(varbank!=null)
asmAddr?.varbank?.parent = sub
if(srcSub.asmAddress==null) { if(srcSub.asmAddress==null) {
var combinedTrueAsm = "" var combinedTrueAsm = ""

View File

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

View File

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

View File

@ -177,11 +177,13 @@ private fun AsmsubroutineContext.toAst(): Subroutine {
private fun RomsubroutineContext.toAst(): Subroutine { private fun RomsubroutineContext.toAst(): Subroutine {
val subdecl = asmsub_decl().toAst() 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 addr = address.toAst().number.toUInt()
val address = Subroutine.Address(constbank, varbank, addr)
return Subroutine(subdecl.name, subdecl.parameters.toMutableList(), subdecl.returntypes.toMutableList(), return Subroutine(subdecl.name, subdecl.parameters.toMutableList(), subdecl.returntypes.toMutableList(),
subdecl.asmParameterRegisters, subdecl.asmReturnvaluesRegisters, 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 val targetAsmAddress = (target as? Subroutine)?.asmAddress
if(targetAsmAddress!=null) { if(targetAsmAddress!=null) {
return NumericLiteral(DataType.UWORD, targetAsmAddress.second.toDouble(), position) return NumericLiteral(DataType.UWORD, targetAsmAddress.address.toDouble(), position)
} }
return null return null
} }

View File

@ -806,7 +806,7 @@ class Subroutine(override val name: String,
val asmParameterRegisters: List<RegisterOrStatusflag>, val asmParameterRegisters: List<RegisterOrStatusflag>,
val asmReturnvaluesRegisters: List<RegisterOrStatusflag>, val asmReturnvaluesRegisters: List<RegisterOrStatusflag>,
val asmClobbers: Set<CpuRegister>, val asmClobbers: Set<CpuRegister>,
val asmAddress: Pair<UByte?, UInt>?, // bank, address val asmAddress: Address?,
val isAsmSubroutine: Boolean, val isAsmSubroutine: Boolean,
var inline: Boolean, var inline: Boolean,
var hasBeenInlined: Boolean=false, var hasBeenInlined: Boolean=false,
@ -818,6 +818,7 @@ class Subroutine(override val name: String,
override fun linkParents(parent: Node) { override fun linkParents(parent: Node) {
this.parent = parent this.parent = parent
this.asmAddress?.varbank?.linkParents(this)
parameters.forEach { it.linkParents(this) } parameters.forEach { it.linkParents(this) }
statements.forEach { it.linkParents(this) } statements.forEach { it.linkParents(this) }
} }
@ -834,11 +835,18 @@ class Subroutine(override val name: String,
statements[idx] = replacement statements[idx] = replacement
replacement.parent = this 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") else -> throw FatalAstException("can't replace")
} }
} }
override fun referencesIdentifier(nameInSource: List<String>): Boolean = override fun referencesIdentifier(nameInSource: List<String>): Boolean =
asmAddress?.varbank?.referencesIdentifier(nameInSource)==true ||
statements.any { it.referencesIdentifier(nameInSource) } || statements.any { it.referencesIdentifier(nameInSource) } ||
parameters.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 accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
override fun toString() = override fun toString() =
"Subroutine(name=$name, parameters=$parameters, returntypes=$returntypes, ${statements.size} statements, address=$asmAddress)" "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, open class SubroutineParameter(val name: String,

View File

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

View File

@ -45,6 +45,7 @@ interface IAstVisitor {
} }
fun visit(subroutine: Subroutine) { fun visit(subroutine: Subroutine) {
subroutine.asmAddress?.varbank?.accept(this)
subroutine.statements.forEach { it.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. - 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. 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 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. 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: **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. See :ref:`banking` for more information.
.. note:: .. 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 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 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>`` 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 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 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 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. 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. 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>`_ . 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 TODO
==== ====
make @bank accept a variable as well to make it dynamic
rename 'romsub' to 'extsub' ? keep romsub as alias? 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. 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() { sub start() {
; load the example libraries in hiram banks 4 and 5 ; 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) cx16.rambank(4)
void diskio.load("library1.prg", $a000) void diskio.load("library1.prg", $a000)
cx16.rambank(5) cx16.rambank(5)

View File

@ -1,23 +1,31 @@
%import textio %import textio
%zeropage basicsafe %zeropage basicsafe
%option no_sysinit
main { main {
sub start() { ubyte bank
basic_area.routine1()
}
}
basic_area $a000 { romsub @bank bank $a000 = routine_in_hiram(uword arg @AY) -> uword @AY
sub routine1() {
txt.print("hello from basic rom area ") sub start() {
txt.print_uwhex(&routine1, true) ; 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() txt.nl()
} }
}
;hiram_area $c000 { asmsub the_increment_routine(uword arg @AY) -> uword @AY {
; %option force_output %asm {{
; sub thing() { clc
; cx16.r0++ adc #1
; } bcc +
;} iny
+ rts
}}
}
}

View File

@ -294,7 +294,7 @@ asmsubroutine :
; ;
romsubroutine : 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? ; asmsub_decl : identifier '(' asmsub_params? ')' asmsub_clobbers? asmsub_returns? ;