mirror of
https://github.com/irmen/prog8.git
synced 2025-01-01 23:30:08 +00:00
romsub @bank now also accepts a variable so the bank can be dynamic
This commit is contained in:
parent
491e5dbcfb
commit
77e376f6bf
@ -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) :
|
||||
|
@ -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 -> {
|
||||
|
@ -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(
|
||||
|
@ -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)
|
||||
|
@ -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"""
|
||||
)
|
||||
}
|
||||
|
@ -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")
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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)
|
||||
)
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -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")
|
||||
|
||||
|
@ -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 = ""
|
||||
|
@ -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} (")
|
||||
|
@ -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")
|
||||
|
@ -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()
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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,
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -45,6 +45,7 @@ interface IAstVisitor {
|
||||
}
|
||||
|
||||
fun visit(subroutine: Subroutine) {
|
||||
subroutine.asmAddress?.varbank?.accept(this)
|
||||
subroutine.statements.forEach { it.accept(this) }
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
-------------
|
||||
|
@ -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::
|
||||
|
@ -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>`_ .
|
||||
|
||||
|
||||
|
@ -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.
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
}}
|
||||
}
|
||||
}
|
||||
|
@ -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? ;
|
||||
|
Loading…
Reference in New Issue
Block a user