mirror of
https://github.com/irmen/prog8.git
synced 2024-05-31 15:41:34 +00:00
better checking for number of return values
assignment optimization if return register already is the same as the assignment target
This commit is contained in:
parent
5f11f485a2
commit
98acff802f
|
@ -210,11 +210,6 @@ class PtFunctionCall(val name: String,
|
||||||
val void: Boolean,
|
val void: Boolean,
|
||||||
type: DataType,
|
type: DataType,
|
||||||
position: Position) : PtExpression(type, position) {
|
position: Position) : PtExpression(type, position) {
|
||||||
init {
|
|
||||||
if(!void)
|
|
||||||
require(type!=DataType.UNDEFINED)
|
|
||||||
}
|
|
||||||
|
|
||||||
val args: List<PtExpression>
|
val args: List<PtExpression>
|
||||||
get() = children.map { it as PtExpression }
|
get() = children.map { it as PtExpression }
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,7 +10,7 @@ fun printAst(root: PtNode, skipLibraries: Boolean, output: (text: String) -> Uni
|
||||||
fun type(dt: DataType) = "!${dt.name.lowercase()}!"
|
fun type(dt: DataType) = "!${dt.name.lowercase()}!"
|
||||||
fun txt(node: PtNode): String {
|
fun txt(node: PtNode): String {
|
||||||
return when(node) {
|
return when(node) {
|
||||||
is PtAssignTarget -> "<target>"
|
is PtAssignTarget -> if(node.void) "<void>" else "<target>"
|
||||||
is PtAssignment -> "<assign>"
|
is PtAssignment -> "<assign>"
|
||||||
is PtAugmentedAssign -> "<inplace-assign> ${node.operator}"
|
is PtAugmentedAssign -> "<inplace-assign> ${node.operator}"
|
||||||
is PtBreakpoint -> "%breakpoint"
|
is PtBreakpoint -> "%breakpoint"
|
||||||
|
|
|
@ -1,13 +1,18 @@
|
||||||
package prog8.code.optimize
|
package prog8.code.optimize
|
||||||
|
|
||||||
|
import prog8.code.StRomSub
|
||||||
|
import prog8.code.SymbolTable
|
||||||
import prog8.code.ast.*
|
import prog8.code.ast.*
|
||||||
import prog8.code.core.*
|
import prog8.code.core.*
|
||||||
|
|
||||||
|
|
||||||
fun optimizeIntermediateAst(program: PtProgram, options: CompilationOptions, errors: IErrorReporter) {
|
fun optimizeIntermediateAst(program: PtProgram, options: CompilationOptions, st: SymbolTable, errors: IErrorReporter) {
|
||||||
if (!options.optimize)
|
if (!options.optimize)
|
||||||
return
|
return
|
||||||
while(errors.noErrors() && optimizeCommonSubExpressions(program, errors)>0) {
|
while (errors.noErrors() &&
|
||||||
|
(optimizeCommonSubExpressions(program, errors)
|
||||||
|
+ optimizeAssignTargets(program, st, errors)) > 0
|
||||||
|
) {
|
||||||
// keep rolling
|
// keep rolling
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -109,6 +114,79 @@ private fun optimizeCommonSubExpressions(program: PtProgram, errors: IErrorRepor
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private fun optimizeAssignTargets(program: PtProgram, st: SymbolTable, errors: IErrorReporter): Int {
|
||||||
|
var changes = 0
|
||||||
|
walkAst(program) { node: PtNode, depth: Int ->
|
||||||
|
if(node is PtAssignment) {
|
||||||
|
val value = node.value
|
||||||
|
val functionName = when(value) {
|
||||||
|
is PtBuiltinFunctionCall -> value.name
|
||||||
|
is PtFunctionCall -> value.name
|
||||||
|
else -> null
|
||||||
|
}
|
||||||
|
if(functionName!=null) {
|
||||||
|
val stNode = st.lookup(functionName)
|
||||||
|
if (stNode is StRomSub) {
|
||||||
|
require(node.children.size==stNode.returns.size+1) {
|
||||||
|
"number of targets must match return values"
|
||||||
|
}
|
||||||
|
node.children.zip(stNode.returns).withIndex().forEach { (index, xx) ->
|
||||||
|
val target = xx.first as PtAssignTarget
|
||||||
|
val returnedRegister = xx.second.register.registerOrPair
|
||||||
|
if(returnedRegister!=null && !target.void && target.identifier!=null) {
|
||||||
|
if(isSame(target.identifier!!, xx.second.type, returnedRegister)) {
|
||||||
|
// output register is already identical to target register, so it can become void
|
||||||
|
val voidTarget = PtAssignTarget(true, target.position)
|
||||||
|
node.children[index] = voidTarget
|
||||||
|
voidTarget.parent = node
|
||||||
|
changes++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(node.children.dropLast(1).all { (it as PtAssignTarget).void }) {
|
||||||
|
// all targets are now void, the whole assignment can be discarded and replaced by just a (void) call to the subroutine
|
||||||
|
val index = node.parent.children.indexOf(node)
|
||||||
|
val voidCall = PtFunctionCall(functionName, true, value.type, value.position)
|
||||||
|
value.children.forEach { voidCall.add(it) }
|
||||||
|
node.parent.children[index] = voidCall
|
||||||
|
voidCall.parent = node.parent
|
||||||
|
changes++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
true
|
||||||
|
}
|
||||||
|
return changes
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun isSame(identifier: PtIdentifier, type: DataType, returnedRegister: RegisterOrPair): Boolean {
|
||||||
|
if(returnedRegister in Cx16VirtualRegisters) {
|
||||||
|
val regname = returnedRegister.name.lowercase()
|
||||||
|
val identifierRegName = identifier.name.substringAfterLast('.')
|
||||||
|
/*
|
||||||
|
cx16.r? UWORD
|
||||||
|
cx16.r?s WORD
|
||||||
|
cx16.r?L UBYTE
|
||||||
|
cx16.r?H UBYTE
|
||||||
|
cx16.r?sL BYTE
|
||||||
|
cx16.r?sH BYTE
|
||||||
|
*/
|
||||||
|
if(identifier.type in ByteDatatypes && type in ByteDatatypes) {
|
||||||
|
if(identifier.name.startsWith("cx16.$regname") && identifierRegName.startsWith(regname)) {
|
||||||
|
return identifierRegName.substring(2) in arrayOf("", "L", "sL") // note: not the -H (msb) variants!
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if(identifier.type in WordDatatypes && type in WordDatatypes) {
|
||||||
|
if(identifier.name.startsWith("cx16.$regname") && identifierRegName.startsWith(regname)) {
|
||||||
|
return identifierRegName.substring(2) in arrayOf("", "s")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false // there are no identifiers directly corresponding to cpu registers
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
internal fun findScopeName(node: PtNode): String {
|
internal fun findScopeName(node: PtNode): String {
|
||||||
var parent=node
|
var parent=node
|
||||||
while(parent !is PtNamedNode)
|
while(parent !is PtNamedNode)
|
||||||
|
|
|
@ -112,7 +112,11 @@ internal class AssignmentAsmGen(private val program: PtProgram,
|
||||||
val tgt = AsmAssignTarget.fromAstAssignment(target, target.definingISub(), asmgen)
|
val tgt = AsmAssignTarget.fromAstAssignment(target, target.definingISub(), asmgen)
|
||||||
when(returns.type) {
|
when(returns.type) {
|
||||||
in ByteDatatypesWithBoolean -> {
|
in ByteDatatypesWithBoolean -> {
|
||||||
assignRegisterByte(tgt, returns.register.registerOrPair!!.asCpuRegister(), false, false)
|
if(returns.register.registerOrPair in Cx16VirtualRegisters) {
|
||||||
|
assignVirtualRegister(tgt, returns.register.registerOrPair!!)
|
||||||
|
} else {
|
||||||
|
assignRegisterByte(tgt, returns.register.registerOrPair!!.asCpuRegister(), false, false)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
in WordDatatypes -> {
|
in WordDatatypes -> {
|
||||||
assignRegisterpairWord(tgt, returns.register.registerOrPair!!)
|
assignRegisterpairWord(tgt, returns.register.registerOrPair!!)
|
||||||
|
|
|
@ -235,7 +235,7 @@ save_end:
|
||||||
|
|
||||||
sub read_scanline(uword size) {
|
sub read_scanline(uword size) {
|
||||||
while size!=0 {
|
while size!=0 {
|
||||||
cx16.r0 = cx16.MACPTR(min(255, size) as ubyte, &cx16.VERA_DATA0, true)
|
void, cx16.r0 = cx16.MACPTR(min(255, size) as ubyte, &cx16.VERA_DATA0, true)
|
||||||
if_cs {
|
if_cs {
|
||||||
; no MACPTR support
|
; no MACPTR support
|
||||||
repeat size
|
repeat size
|
||||||
|
@ -297,7 +297,7 @@ save_end:
|
||||||
cx16.r0L = lsb(size)
|
cx16.r0L = lsb(size)
|
||||||
if msb(size)!=0
|
if msb(size)!=0
|
||||||
cx16.r0L = 0 ; 256 bytes
|
cx16.r0L = 0 ; 256 bytes
|
||||||
cx16.r0 = cx16.MCIOUT(cx16.r0L, &cx16.VERA_DATA0, true)
|
void, cx16.r0 = cx16.MCIOUT(cx16.r0L, &cx16.VERA_DATA0, true)
|
||||||
if_cs {
|
if_cs {
|
||||||
; no MCIOUT support
|
; no MCIOUT support
|
||||||
repeat size
|
repeat size
|
||||||
|
|
|
@ -331,7 +331,7 @@ close_end:
|
||||||
readsize = 255
|
readsize = 255
|
||||||
if num_bytes<readsize
|
if num_bytes<readsize
|
||||||
readsize = num_bytes
|
readsize = num_bytes
|
||||||
readsize = cx16.MACPTR(lsb(readsize), bufferpointer, false) ; fast block reads
|
void, readsize = cx16.MACPTR(lsb(readsize), bufferpointer, false) ; fast block reads
|
||||||
if_cs
|
if_cs
|
||||||
goto byte_read_loop ; MACPTR block read not supported, do fallback loop
|
goto byte_read_loop ; MACPTR block read not supported, do fallback loop
|
||||||
list_blocks += readsize
|
list_blocks += readsize
|
||||||
|
@ -468,7 +468,7 @@ _end rts
|
||||||
if num_bytes!=0 {
|
if num_bytes!=0 {
|
||||||
reset_write_channel()
|
reset_write_channel()
|
||||||
do {
|
do {
|
||||||
cx16.r0 = cx16.MCIOUT(lsb(num_bytes), bufferpointer, false) ; fast block writes
|
void, cx16.r0 = cx16.MCIOUT(lsb(num_bytes), bufferpointer, false) ; fast block writes
|
||||||
if_cs
|
if_cs
|
||||||
goto no_mciout
|
goto no_mciout
|
||||||
num_bytes -= cx16.r0
|
num_bytes -= cx16.r0
|
||||||
|
|
|
@ -8,6 +8,7 @@ import prog8.ast.expressions.Expression
|
||||||
import prog8.ast.expressions.NumericLiteral
|
import prog8.ast.expressions.NumericLiteral
|
||||||
import prog8.ast.printProgram
|
import prog8.ast.printProgram
|
||||||
import prog8.ast.statements.Directive
|
import prog8.ast.statements.Directive
|
||||||
|
import prog8.code.SymbolTable
|
||||||
import prog8.code.SymbolTableMaker
|
import prog8.code.SymbolTableMaker
|
||||||
import prog8.code.ast.PtProgram
|
import prog8.code.ast.PtProgram
|
||||||
import prog8.code.ast.printAst
|
import prog8.code.ast.printAst
|
||||||
|
@ -134,8 +135,12 @@ fun compileProgram(args: CompilerArguments): CompilationResult? {
|
||||||
}
|
}
|
||||||
|
|
||||||
val intermediateAst = IntermediateAstMaker(program, args.errors).transform()
|
val intermediateAst = IntermediateAstMaker(program, args.errors).transform()
|
||||||
optimizeIntermediateAst(intermediateAst, compilationOptions, args.errors)
|
val stMaker = SymbolTableMaker(intermediateAst, compilationOptions)
|
||||||
args.errors.report()
|
val symbolTable = stMaker.make()
|
||||||
|
if(compilationOptions.optimize) {
|
||||||
|
optimizeIntermediateAst(intermediateAst, compilationOptions, symbolTable, args.errors)
|
||||||
|
args.errors.report()
|
||||||
|
}
|
||||||
|
|
||||||
if(args.printAst2) {
|
if(args.printAst2) {
|
||||||
println("\n*********** INTERMEDIATE AST *************")
|
println("\n*********** INTERMEDIATE AST *************")
|
||||||
|
@ -143,7 +148,7 @@ fun compileProgram(args: CompilerArguments): CompilationResult? {
|
||||||
println("*********** INTERMEDIATE AST END *************\n")
|
println("*********** INTERMEDIATE AST END *************\n")
|
||||||
}
|
}
|
||||||
|
|
||||||
if(!createAssemblyAndAssemble(intermediateAst, args.errors, compilationOptions)) {
|
if(!createAssemblyAndAssemble(intermediateAst, symbolTable, args.errors, compilationOptions)) {
|
||||||
System.err.println("Error in codegeneration or assembler")
|
System.err.println("Error in codegeneration or assembler")
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
@ -464,6 +469,7 @@ private fun postprocessAst(program: Program, errors: IErrorReporter, compilerOpt
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun createAssemblyAndAssemble(program: PtProgram,
|
private fun createAssemblyAndAssemble(program: PtProgram,
|
||||||
|
symbolTable: SymbolTable,
|
||||||
errors: IErrorReporter,
|
errors: IErrorReporter,
|
||||||
compilerOptions: CompilationOptions
|
compilerOptions: CompilationOptions
|
||||||
): Boolean {
|
): Boolean {
|
||||||
|
@ -477,8 +483,6 @@ private fun createAssemblyAndAssemble(program: PtProgram,
|
||||||
else
|
else
|
||||||
throw NotImplementedError("no code generator for cpu ${compilerOptions.compTarget.machine.cpu}")
|
throw NotImplementedError("no code generator for cpu ${compilerOptions.compTarget.machine.cpu}")
|
||||||
|
|
||||||
val stMaker = SymbolTableMaker(program, compilerOptions)
|
|
||||||
val symbolTable = stMaker.make()
|
|
||||||
val assembly = asmgen.generate(program, symbolTable, compilerOptions, errors)
|
val assembly = asmgen.generate(program, symbolTable, compilerOptions, errors)
|
||||||
errors.report()
|
errors.report()
|
||||||
|
|
||||||
|
|
|
@ -579,12 +579,8 @@ internal class AstChecker(private val program: Program,
|
||||||
checkMultiAssignment(assignment, fcall, fcallTarget)
|
checkMultiAssignment(assignment, fcall, fcallTarget)
|
||||||
} else if(fcallTarget!=null) {
|
} else if(fcallTarget!=null) {
|
||||||
if(fcallTarget.returntypes.size!=1) {
|
if(fcallTarget.returntypes.size!=1) {
|
||||||
// If there are 2 return values, one of them being a boolean in a status register, this is okay.
|
errors.err("number of assignment targets doesn't match number of return values from the subroutine", fcall.position)
|
||||||
// In that case the normal value is assigned and the status bit is dealth with separately for example with if_cs
|
return
|
||||||
val (returnRegisters, _) = fcallTarget.asmReturnvaluesRegisters.partition { rr -> rr.registerOrPair != null }
|
|
||||||
if(returnRegisters.size>1) {
|
|
||||||
errors.err("multiple return values and too few assignment targets, need at least ${returnRegisters.size}", fcall.position)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -600,7 +596,7 @@ internal class AstChecker(private val program: Program,
|
||||||
}
|
}
|
||||||
val targets = assignment.target.multi!!
|
val targets = assignment.target.multi!!
|
||||||
if(fcallTarget.returntypes.size!=targets.size) {
|
if(fcallTarget.returntypes.size!=targets.size) {
|
||||||
errors.err("number of assignment targets doesn't match number of return values", fcall.position)
|
errors.err("number of assignment targets doesn't match number of return values from the subroutine", fcall.position)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
fcallTarget.returntypes.zip(targets).withIndex().forEach { (index, p) ->
|
fcallTarget.returntypes.zip(targets).withIndex().forEach { (index, p) ->
|
||||||
|
|
|
@ -270,7 +270,7 @@ class IntermediateAstMaker(private val program: Program, private val errors: IEr
|
||||||
|
|
||||||
private fun transform(srcCall: FunctionCallStatement): PtFunctionCall {
|
private fun transform(srcCall: FunctionCallStatement): PtFunctionCall {
|
||||||
val (target, type) = srcCall.target.targetNameAndType(program)
|
val (target, type) = srcCall.target.targetNameAndType(program)
|
||||||
val call = PtFunctionCall(target,true, type, srcCall.position)
|
val call = PtFunctionCall(target, true, type, srcCall.position)
|
||||||
for (arg in srcCall.args)
|
for (arg in srcCall.args)
|
||||||
call.add(transformExpression(arg))
|
call.add(transformExpression(arg))
|
||||||
return call
|
return call
|
||||||
|
@ -279,7 +279,7 @@ class IntermediateAstMaker(private val program: Program, private val errors: IEr
|
||||||
private fun transform(srcCall: FunctionCallExpression): PtFunctionCall {
|
private fun transform(srcCall: FunctionCallExpression): PtFunctionCall {
|
||||||
val (target, _) = srcCall.target.targetNameAndType(program)
|
val (target, _) = srcCall.target.targetNameAndType(program)
|
||||||
val iType = srcCall.inferType(program)
|
val iType = srcCall.inferType(program)
|
||||||
val call = PtFunctionCall(target, iType.isUnknown, iType.getOrElse { DataType.UNDEFINED }, srcCall.position)
|
val call = PtFunctionCall(target, iType.isUnknown && srcCall.parent !is Assignment, iType.getOrElse { DataType.UNDEFINED }, srcCall.position)
|
||||||
for (arg in srcCall.args)
|
for (arg in srcCall.args)
|
||||||
call.add(transformExpression(arg))
|
call.add(transformExpression(arg))
|
||||||
return call
|
return call
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
TODO
|
TODO
|
||||||
====
|
====
|
||||||
|
|
||||||
remove redundant assignments in calls like this where the result registers are the same as the assigment targets:
|
return cx16.MACPTR(0, 2, true) -> should give compiler error about number of values
|
||||||
void, cx16.r0s, cx16.r1s = cx16.mouse_pos()
|
|
||||||
(6502 + IR)
|
check docs on assign about status register in assignment (can no longer be ignored, use void to not assign it)
|
||||||
|
|
||||||
...
|
...
|
||||||
|
|
||||||
|
|
|
@ -73,7 +73,8 @@ waitkey:
|
||||||
; test_stack.test()
|
; test_stack.test()
|
||||||
}
|
}
|
||||||
|
|
||||||
ubyte key=cbm.GETIN()
|
ubyte key
|
||||||
|
void, key=cbm.GETIN()
|
||||||
if key==0 goto waitkey
|
if key==0 goto waitkey
|
||||||
|
|
||||||
keypress(key)
|
keypress(key)
|
||||||
|
@ -252,7 +253,7 @@ waitkey:
|
||||||
ubyte key = 0
|
ubyte key = 0
|
||||||
while key!=133 {
|
while key!=133 {
|
||||||
; endless loop until user presses F1 to restart the game
|
; endless loop until user presses F1 to restart the game
|
||||||
key = cbm.GETIN()
|
void, key = cbm.GETIN()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -182,7 +182,7 @@ processchunk_call jsr $ffff ; modified
|
||||||
ubyte readsize = 255
|
ubyte readsize = 255
|
||||||
if msb(size)==0
|
if msb(size)==0
|
||||||
readsize = lsb(size)
|
readsize = lsb(size)
|
||||||
cx16.r2 = cx16.MACPTR(readsize, bonkbuffer, false) ; can't MACPTR directly to bonk ram
|
void, cx16.r2 = cx16.MACPTR(readsize, bonkbuffer, false) ; can't MACPTR directly to bonk ram
|
||||||
cx16.rombank(bonk)
|
cx16.rombank(bonk)
|
||||||
sys.memcopy(bonkbuffer, cx16.r3, cx16.r2) ; copy to bonk ram
|
sys.memcopy(bonkbuffer, cx16.r3, cx16.r2) ; copy to bonk ram
|
||||||
cx16.rombank(orig_rom_bank)
|
cx16.rombank(orig_rom_bank)
|
||||||
|
|
|
@ -95,7 +95,8 @@ waitkey:
|
||||||
; test_stack.test()
|
; test_stack.test()
|
||||||
}
|
}
|
||||||
|
|
||||||
ubyte key=cbm.GETIN()
|
ubyte key
|
||||||
|
void, key=cbm.GETIN()
|
||||||
keypress(key)
|
keypress(key)
|
||||||
cx16.r0,void = cx16.joystick_get(1)
|
cx16.r0,void = cx16.joystick_get(1)
|
||||||
joystick(cx16.r0)
|
joystick(cx16.r0)
|
||||||
|
@ -338,7 +339,7 @@ waitkey:
|
||||||
cx16.r0, void = cx16.joystick_get(1)
|
cx16.r0, void = cx16.joystick_get(1)
|
||||||
if cx16.r0 & %0000000000010000 == 0
|
if cx16.r0 & %0000000000010000 == 0
|
||||||
break
|
break
|
||||||
key = cbm.GETIN()
|
void, key = cbm.GETIN()
|
||||||
} until key==133
|
} until key==133
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -66,7 +66,8 @@ main {
|
||||||
vtui.print_str2("arrow keys to move!", $61, true)
|
vtui.print_str2("arrow keys to move!", $61, true)
|
||||||
|
|
||||||
char_loop:
|
char_loop:
|
||||||
ubyte char = cbm.GETIN()
|
ubyte char
|
||||||
|
void, char = cbm.GETIN()
|
||||||
if char==0
|
if char==0
|
||||||
goto char_loop
|
goto char_loop
|
||||||
|
|
||||||
|
|
|
@ -1,19 +1,25 @@
|
||||||
%import textio
|
|
||||||
%zeropage basicsafe
|
%zeropage basicsafe
|
||||||
%option no_sysinit
|
%option no_sysinit
|
||||||
|
|
||||||
main {
|
main {
|
||||||
sub start() {
|
romsub $2000 = func1() clobbers(X) -> ubyte @A, word @R0, byte @R1
|
||||||
cx16.mouse_config2(1)
|
romsub $3000 = func2() clobbers(X) -> ubyte @A, uword @R0, uword @R1
|
||||||
wait_mousebutton()
|
romsub $4000 = func3() clobbers(X) -> ubyte @R0
|
||||||
|
|
||||||
sub wait_mousebutton() {
|
sub start() {
|
||||||
do {
|
bool flag
|
||||||
cx16.r0L, void, void = cx16.mouse_pos()
|
void cbm.GETIN()
|
||||||
} until cx16.r0L!=0
|
flag, cx16.r1L = cbm.GETIN()
|
||||||
do {
|
|
||||||
cx16.r0L, void, void = cx16.mouse_pos()
|
void, cx16.r0s, cx16.r1sL = func1()
|
||||||
} until cx16.r0L==0
|
void, cx16.r2, cx16.r1 = func2()
|
||||||
}
|
cx16.r0L = func3()
|
||||||
|
cx16.r0H = func3()
|
||||||
|
|
||||||
|
cx16.r0 = readblock()
|
||||||
|
}
|
||||||
|
|
||||||
|
sub readblock() -> uword {
|
||||||
|
return cx16.MACPTR(0, 2, true) ; TODO compiler error (number of return values)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue
Block a user