From fad74a6ae0a89443e235e0654540f81ca6fe647f Mon Sep 17 00:00:00 2001 From: Irmen de Jong Date: Sun, 18 Nov 2018 23:26:05 +0100 Subject: [PATCH] tweaking multiple assignment targets --- compiler/examples/test.p8 | 24 ++- compiler/src/prog8/ast/AST.kt | 30 +++- compiler/src/prog8/ast/AstChecker.kt | 52 ++++--- compiler/src/prog8/compiler/Compiler.kt | 137 ++++++++++++------ .../src/prog8/compiler/intermediate/Opcode.kt | 3 + .../src/prog8/compiler/target/c64/AsmGen.kt | 58 +++++++- compiler/src/prog8/stackvm/StackVm.kt | 15 ++ prog8lib/c64lib.p8 | 10 +- 8 files changed, 241 insertions(+), 88 deletions(-) diff --git a/compiler/examples/test.p8 b/compiler/examples/test.p8 index 008329291..bc7705967 100644 --- a/compiler/examples/test.p8 +++ b/compiler/examples/test.p8 @@ -11,16 +11,24 @@ sub start() { ubyte v2 float f2 uword address + memory uword memaddr = $c000 + uword[2] wordarray + byte b1 - v1=foo() - X, Y =c64.GETADR() - v1, v2 =c64.GETADR() - address =c64.MEMBOT(1, 0.w) - address =c64.IOBASE() - A = c64.CHRIN() - X = c64.CHRIN() - v1 = c64.CHRIN() + ;v1=foo() ; @todo fix return type value error see sub + + A, Y =c64.GETADR() ; ok! + Y, A =c64.GETADR() ; ok! + address = c64flt.GETADRAY() ; ok! + memaddr = c64flt.GETADRAY() ; ok! + wordarray[1] = c64flt.GETADRAY() ; ok! + v1, v2 =c64.GETADR() ; ok! + address =c64.MEMBOT(1, 0.w) ; ok ! + address =c64.IOBASE() ; ok! + A = c64.CHRIN() ; ok ! + X = c64.CHRIN() ; ok ! + v1 = c64.CHRIN() ; ok ! return } diff --git a/compiler/src/prog8/ast/AST.kt b/compiler/src/prog8/ast/AST.kt index c85b38ef1..29770cf03 100644 --- a/compiler/src/prog8/ast/AST.kt +++ b/compiler/src/prog8/ast/AST.kt @@ -394,7 +394,6 @@ interface INameScope { } } - private object ParentSentinel : Node { override val position = Position("<>", 0, 0, 0) override var parent: Node = this @@ -1436,7 +1435,7 @@ class InlineAssembly(val assembly: String, override val position: Position) : IS } -class RegisterOrStatusflag(val registerOrPair: RegisterOrPair?, val statusflag: Statusflag?) +data class RegisterOrStatusflag(val registerOrPair: RegisterOrPair?, val statusflag: Statusflag?) class AnonymousScope(override var statements: MutableList, override val position: Position) : INameScope, IStatement { @@ -1592,8 +1591,6 @@ fun prog8Parser.ModuleContext.toAst(name: String) : Module = Module(name, modulestatement().asSequence().map { it.toAst() }.toMutableList(), toPosition()) -/************** Helper extension methods (private) ************/ - private fun ParserRuleContext.toPosition() : Position { val file = Paths.get(this.start.inputStream.sourceName).fileName.toString() // note: be ware of TAB characters in the source text, they count as 1 column... @@ -2056,3 +2053,28 @@ private fun prog8Parser.RepeatloopContext.toAst(): RepeatLoop { val scope = AnonymousScope(statements, statement_block()?.toPosition() ?: statement().toPosition()) return RepeatLoop(scope, untilCondition, toPosition()) } + + +internal fun registerSet(asmReturnvaluesRegisters: Iterable): Set { + val resultRegisters = mutableSetOf() + for(x in asmReturnvaluesRegisters) { + when(x.registerOrPair) { + RegisterOrPair.A -> resultRegisters.add(Register.A) + RegisterOrPair.X -> resultRegisters.add(Register.X) + RegisterOrPair.Y -> resultRegisters.add(Register.Y) + RegisterOrPair.AX -> { + resultRegisters.add(Register.A) + resultRegisters.add(Register.X) + } + RegisterOrPair.AY -> { + resultRegisters.add(Register.A) + resultRegisters.add(Register.Y) + } + RegisterOrPair.XY -> { + resultRegisters.add(Register.X) + resultRegisters.add(Register.Y) + } + } + } + return resultRegisters +} diff --git a/compiler/src/prog8/ast/AstChecker.kt b/compiler/src/prog8/ast/AstChecker.kt index 7c1e56a22..905f36189 100644 --- a/compiler/src/prog8/ast/AstChecker.kt +++ b/compiler/src/prog8/ast/AstChecker.kt @@ -177,7 +177,6 @@ class AstChecker(private val namespace: INameScope, return super.process(label) } - /** * Check subroutine definition */ @@ -300,9 +299,32 @@ class AstChecker(private val namespace: INameScope, /** * Assignment target must be register, or a variable name - * Also check data type compatibility + * Also check data type compatibility and number of values */ override fun process(assignment: Assignment): IStatement { + + // assigning from a functioncall COULD return multiple values (from an asm subroutine) + val stmt = (assignment.value as FunctionCall).target.targetStatement(namespace) + if(stmt is Subroutine && stmt.returntypes.size>1) { + if(stmt.isAsmSubroutine) { + if(stmt.returntypes.size != assignment.targets.size) + checkResult.add(ExpressionError("number of return values doesn't match number of assignment targets", assignment.value.position)) + else { + if(assignment.targets.all{it.register!=null}) { + val returnRegisters = registerSet(stmt.asmReturnvaluesRegisters) + val targetRegisters = assignment.targets.filter { it.register != null }.map { it.register }.toSet() + if (returnRegisters != targetRegisters) + checkResult.add(ExpressionError("asmsub return registers $returnRegisters don't match assignment target registers", assignment.position)) + } + for(thing in stmt.returntypes.zip(assignment.targets)) { + if(thing.second.determineDatatype(namespace, heap, assignment)!=thing.first) + checkResult.add(ExpressionError("return type mismatch for target ${thing.second.shortString()}", assignment.value.position)) + } + } + } else + checkResult.add(ExpressionError("only asmsub subroutines can return multiple values", assignment.value.position)) + } + var resultingAssignment = assignment for (target in assignment.targets) { resultingAssignment = processAssignmentTarget(resultingAssignment, target) @@ -367,31 +389,15 @@ class AstChecker(private val namespace: INameScope, } else { val sourceDatatype: DataType? = assignment.value.resultingDatatype(namespace, heap) if(sourceDatatype==null) { - if(assignment.value is FunctionCall) { - // a functioncall COULD return multiple values (from an asm subroutine), treat that differently - val stmt = (assignment.value as FunctionCall).target.targetStatement(namespace) - if(stmt is Subroutine && stmt.returntypes.size>1) { - if(stmt.isAsmSubroutine) { - if(stmt.returntypes.size != assignment.targets.size) - checkResult.add(ExpressionError("number of return values doesn't match number of assignment targets", assignment.value.position)) - else { - for(thing in stmt.returntypes.zip(assignment.targets)) { - if(thing.second.determineDatatype(namespace, heap, assignment)!=thing.first) - checkResult.add(ExpressionError("return type mismatch for target ${thing.second.shortString()}", assignment.value.position)) - } - } - } else - checkResult.add(ExpressionError("only asmsub subroutines can return multiple values", assignment.value.position)) - } - else + if(assignment.targets.size<=1) { + if (assignment.value is FunctionCall) checkResult.add(ExpressionError("function call doesn't return a suitable value to use in assignment", assignment.value.position)) + else + checkResult.add(ExpressionError("assignment value is invalid or has no proper datatype", assignment.value.position)) } - else - checkResult.add(ExpressionError("assignment value is invalid or has no proper datatype", assignment.value.position)) } - else { + else checkAssignmentCompatible(targetDatatype, sourceDatatype, assignment.value, assignment.position) - } } } return assignment diff --git a/compiler/src/prog8/compiler/Compiler.kt b/compiler/src/prog8/compiler/Compiler.kt index 3d812b2eb..d34834afd 100644 --- a/compiler/src/prog8/compiler/Compiler.kt +++ b/compiler/src/prog8/compiler/Compiler.kt @@ -792,13 +792,18 @@ private class StatementTranslator(private val prog: IntermediateProgram, private fun translateSubroutineCall(subroutine: Subroutine, arguments: List, callPosition: Position) { // evaluate the arguments and assign them into the subroutine's argument variables. - prog.line(callPosition) - for(arg in arguments.zip(subroutine.parameters)) { - translate(arg.first) - val opcode=opcodePopvar(arg.second.type) - prog.instr(opcode, callLabel = subroutine.scopedname+"."+arg.second.name) + + if(subroutine.asmReturnvaluesRegisters.isNotEmpty()) { + TODO("call asmsub by loading registers instead") + } else { + prog.line(callPosition) + for (arg in arguments.zip(subroutine.parameters)) { + translate(arg.first) + val opcode = opcodePopvar(arg.second.type) + prog.instr(opcode, callLabel = subroutine.scopedname + "." + arg.second.name) + } } - prog.instr(Opcode.CALL, callLabel=subroutine.scopedname) + prog.instr(Opcode.CALL, callLabel = subroutine.scopedname) } private fun translateBinaryOperator(operator: String, dt: DataType) { @@ -1080,7 +1085,6 @@ private class StatementTranslator(private val prog: IntermediateProgram, } } - private fun translate(stmt: VariableInitializationAssignment) { // this is an assignment to initialize a variable's value in the scope. // the compiler can perhaps optimize this phase. @@ -1089,48 +1093,16 @@ private class StatementTranslator(private val prog: IntermediateProgram, } private fun translate(stmt: Assignment) { - val assignTarget= stmt.singleTarget + prog.line(stmt.position) + translate(stmt.value) + val assignTarget= stmt.singleTarget if(assignTarget==null) { // we're dealing with multiple return values - val targetStmt = (stmt.value as? FunctionCall)?.target?.targetStatement(namespace) - if(targetStmt is Subroutine && targetStmt.isAsmSubroutine) { - // we're dealing with the one case where multiple assignment targets are allowed: a call to an asmsub with multiple return values - // for now, we only support multiple return values as long as they're returned in registers as well. - if(targetStmt.asmReturnvaluesRegisters.isEmpty()) - throw CompilerException("we only support multiple return values / assignment when the asmsub returns values in registers") - // if the result registers are not assigned in the exact same registers, or in variables, we need some code - if(stmt.targets.all{it.register!=null}) { - val resultRegisters = mutableListOf() - for(x in targetStmt.asmReturnvaluesRegisters) { - when(x.registerOrPair) { - RegisterOrPair.A -> resultRegisters.add(Register.A) - RegisterOrPair.X -> resultRegisters.add(Register.X) - RegisterOrPair.Y -> resultRegisters.add(Register.Y) - RegisterOrPair.AX -> { - resultRegisters.add(Register.A) - resultRegisters.add(Register.X) - } - RegisterOrPair.AY -> { - resultRegisters.add(Register.A) - resultRegisters.add(Register.Y) - } - RegisterOrPair.XY -> { - resultRegisters.add(Register.X) - resultRegisters.add(Register.Y) - } - } - } - TODO("$resultRegisters") - } else { - TODO("store results from registers ${targetStmt.asmReturnvaluesRegisters}") - } - } else throw CompilerException("can only use multiple assignment targets on an asmsub call") + translateMultiReturnAssignment(stmt) return } - prog.line(stmt.position) - translate(stmt.value) val valueDt = stmt.value.resultingDatatype(namespace, heap) val targetDt = assignTarget.determineDatatype(namespace, heap, stmt) if(valueDt!=targetDt) { @@ -1181,11 +1153,90 @@ private class StatementTranslator(private val prog: IntermediateProgram, translateAugAssignOperator(stmt.aug_op, stmt.value.resultingDatatype(namespace, heap)) } + if(stmt.value is FunctionCall) { + val sub = (stmt.value as FunctionCall).target.targetStatement(namespace) + if(sub is Subroutine && sub.asmReturnvaluesRegisters.isNotEmpty()) { + // the subroutine call returns its values in registers + storeRegisterIntoTarget(sub.asmReturnvaluesRegisters.single(), assignTarget, stmt) + return + } + } + // pop the result value back into the assignment target val datatype = assignTarget.determineDatatype(namespace, heap, stmt)!! popValueIntoTarget(assignTarget, datatype) } + private fun translateMultiReturnAssignment(stmt: Assignment) { + val targetStmt = (stmt.value as? FunctionCall)?.target?.targetStatement(namespace) + if(targetStmt is Subroutine && targetStmt.isAsmSubroutine) { + // we're dealing with the one case where multiple assignment targets are allowed: a call to an asmsub with multiple return values + // for now, we only support multiple return values as long as they're returned in registers as well. + if(targetStmt.asmReturnvaluesRegisters.isEmpty()) + throw CompilerException("we only support multiple return values / assignment when the asmsub returns values in registers") + // if the result registers are not assigned in the exact same registers, or in variables, we need some code + if(stmt.targets.all{it.register!=null}) { + val resultRegisters = registerSet(targetStmt.asmReturnvaluesRegisters) + if(stmt.targets.size!=resultRegisters.size) + throw CompilerException("asmsub number of return values doesn't match number of assignment targets ${stmt.position}") + val targetRegs = stmt.targets.filter {it.register!=null}.map{it.register}.toSet() + if(resultRegisters!=targetRegs) + throw CompilerException("asmsub return registers don't match assignment target registers ${stmt.position}") + // output is in registers already, no need to emit any asm code + } else { + // output is in registers but has to be stored somewhere + for(result in targetStmt.asmReturnvaluesRegisters.zip(stmt.targets)) + storeRegisterIntoTarget(result.first, result.second, stmt) + } + } else throw CompilerException("can only use multiple assignment targets on an asmsub call") + } + + private fun storeRegisterIntoTarget(registerOrStatus: RegisterOrStatusflag, target: AssignTarget, parent: IStatement) { + if(registerOrStatus.statusflag!=null) + return + when(registerOrStatus.registerOrPair){ + RegisterOrPair.A -> { + val assignment = Assignment(listOf(target), null, RegisterExpr(Register.A, target.position), target.position) + assignment.linkParents(parent) + translate(assignment) + } + RegisterOrPair.X -> { + val assignment = Assignment(listOf(target), null, RegisterExpr(Register.X, target.position), target.position) + assignment.linkParents(parent) + translate(assignment) + } + RegisterOrPair.Y -> { + val assignment = Assignment(listOf(target), null, RegisterExpr(Register.Y, target.position), target.position) + assignment.linkParents(parent) + translate(assignment) + } + RegisterOrPair.AX -> { + // deal with register pair AX: target = A + X*256 + val targetDt = target.determineDatatype(namespace, heap, parent) + if(targetDt!=DataType.UWORD) + throw CompilerException("invalid target datatype for registerpair $targetDt") + prog.instr(Opcode.PUSH_REGAX_WORD) + popValueIntoTarget(target, targetDt) + } + RegisterOrPair.AY -> { + // deal with register pair AY: target = A + Y*256 + val targetDt = target.determineDatatype(namespace, heap, parent) + if(targetDt!=DataType.UWORD) + throw CompilerException("invalid target datatype for registerpair $targetDt") + prog.instr(Opcode.PUSH_REGAY_WORD) + popValueIntoTarget(target, targetDt) + } + RegisterOrPair.XY -> { + // deal with register pair XY: target = X + Y*256 + val targetDt = target.determineDatatype(namespace, heap, parent) + if(targetDt!=DataType.UWORD) + throw CompilerException("invalid target datatype for registerpair $targetDt") + prog.instr(Opcode.PUSH_REGXY_WORD) + popValueIntoTarget(target, targetDt) + } + } + } + private fun popValueIntoTarget(assignTarget: AssignTarget, datatype: DataType) { when { assignTarget.identifier != null -> { diff --git a/compiler/src/prog8/compiler/intermediate/Opcode.kt b/compiler/src/prog8/compiler/intermediate/Opcode.kt index 2d21339b1..d555a1a15 100644 --- a/compiler/src/prog8/compiler/intermediate/Opcode.kt +++ b/compiler/src/prog8/compiler/intermediate/Opcode.kt @@ -14,6 +14,9 @@ enum class Opcode { PUSH_VAR_BYTE, // push byte variable (ubyte, byte) PUSH_VAR_WORD, // push word variable (uword, word) PUSH_VAR_FLOAT, // push float variable + PUSH_REGAX_WORD, // push registers A/X as a 16-bit word + PUSH_REGAY_WORD, // push registers A/Y as a 16-bit word + PUSH_REGXY_WORD, // push registers X/Y as a 16-bit word // popping values off the (evaluation) stack, possibly storing them in another location DISCARD_BYTE, // discard top byte value diff --git a/compiler/src/prog8/compiler/target/c64/AsmGen.kt b/compiler/src/prog8/compiler/target/c64/AsmGen.kt index 6f3e8a752..3f8ee084c 100644 --- a/compiler/src/prog8/compiler/target/c64/AsmGen.kt +++ b/compiler/src/prog8/compiler/target/c64/AsmGen.kt @@ -459,6 +459,10 @@ class AsmGen(val options: CompilationOptions, val program: IntermediateProgram, """ } + Opcode.PUSH_REGAY_WORD -> { + " sta ${ESTACK_LO.toHex()},x | tya | sta ${ESTACK_HI.toHex()},x | dex " + } + Opcode.READ_INDEXED_VAR_BYTE -> { // @todo is this correct? """ ldy ${(ESTACK_LO+1).toHex()},x @@ -998,8 +1002,6 @@ class AsmGen(val options: CompilationOptions, val program: IntermediateProgram, """ private val patterns = listOf( - // -------------- simple conversions ---------------- - // ----------- assignment to BYTE VARIABLE ---------------- // var = (u)bytevalue AsmPattern(listOf(Opcode.PUSH_BYTE, Opcode.POP_VAR_BYTE)) { segment -> @@ -2434,10 +2436,56 @@ class AsmGen(val options: CompilationOptions, val program: IntermediateProgram, ldy #>${hexVal(segment[2])} jsr prog8_lib.copy_float """ + }, + + + // ---------- some special operations ------------------ + // var word = AY register pair + AsmPattern(listOf(Opcode.PUSH_REGAY_WORD, Opcode.POP_VAR_WORD)) { segment -> + """ + sta ${segment[1].callLabel} + sty ${segment[1].callLabel}+1 + """ + }, + // var word = AX register pair + AsmPattern(listOf(Opcode.PUSH_REGAX_WORD, Opcode.POP_VAR_WORD)) { segment -> + """ + sta ${segment[1].callLabel} + stx ${segment[1].callLabel}+1 + """ + }, + // var word = XY register pair + AsmPattern(listOf(Opcode.PUSH_REGXY_WORD, Opcode.POP_VAR_WORD)) { segment -> + """ + stx ${segment[1].callLabel} + sty ${segment[1].callLabel}+1 + """ + }, + // mem word = AY register pair + AsmPattern(listOf(Opcode.PUSH_REGAY_WORD, Opcode.POP_MEM_WORD)) { segment -> + """ + sta ${hexVal(segment[1])} + sty ${hexValPlusOne(segment[1])} + """ + }, + // mem word = AX register pair + AsmPattern(listOf(Opcode.PUSH_REGAX_WORD, Opcode.POP_MEM_WORD)) { segment -> + """ + sta ${hexVal(segment[1])} + stx ${hexValPlusOne(segment[1])} + """ + }, + // mem word = XY register pair + AsmPattern(listOf(Opcode.PUSH_REGXY_WORD, Opcode.POP_MEM_WORD)) { segment -> + """ + stx ${hexVal(segment[1])} + sty ${hexValPlusOne(segment[1])} + """ } -// // assignment: floatarray[idxbyte] = float + +// // @todo assignment: floatarray[idxbyte] = float // AsmPattern(listOf(Opcode.PUSH_FLOAT, Opcode.PUSH_BYTE, Opcode.WRITE_INDEXED_VAR_FLOAT)) { segment -> // val floatConst = getFloatConst(segment[0].arg!!) // val index = intVal(segment[1]) * Mflpt5.MemorySize @@ -2451,7 +2499,7 @@ class AsmGen(val options: CompilationOptions, val program: IntermediateProgram, // jsr prog8_lib.copy_float // """ // }, -// // assignment: floatarray[idxbyte] = floatvar +// // @todo assignment: floatarray[idxbyte] = floatvar // AsmPattern(listOf(Opcode.PUSH_VAR_FLOAT, Opcode.PUSH_BYTE, Opcode.WRITE_INDEXED_VAR_FLOAT)) { segment -> // val index = intVal(segment[1]) * Mflpt5.MemorySize // """ @@ -2464,7 +2512,7 @@ class AsmGen(val options: CompilationOptions, val program: IntermediateProgram, // jsr prog8_lib.copy_float // """ // }, -// // assignment: floatarray[idxbyte] = memfloat +// // @todo assignment: floatarray[idxbyte] = memfloat // AsmPattern(listOf(Opcode.PUSH_MEM_FLOAT, Opcode.PUSH_BYTE, Opcode.WRITE_INDEXED_VAR_FLOAT)) { segment -> // val index = intVal(segment[1]) * Mflpt5.MemorySize // """ diff --git a/compiler/src/prog8/stackvm/StackVm.kt b/compiler/src/prog8/stackvm/StackVm.kt index 322a5aad9..9a9da5c71 100644 --- a/compiler/src/prog8/stackvm/StackVm.kt +++ b/compiler/src/prog8/stackvm/StackVm.kt @@ -762,6 +762,21 @@ class StackVm(private var traceOutputFile: String?) { checkDt(value, DataType.FLOAT) evalstack.push(value) } + Opcode.PUSH_REGAX_WORD -> { + val a=variables["A"]!!.integerValue() + val x=variables["X"]!!.integerValue() + evalstack.push(Value(DataType.UWORD, x*256+a)) + } + Opcode.PUSH_REGAY_WORD -> { + val a=variables["A"]!!.integerValue() + val y=variables["Y"]!!.integerValue() + evalstack.push(Value(DataType.UWORD, y*256+a)) + } + Opcode.PUSH_REGXY_WORD -> { + val x=variables["X"]!!.integerValue() + val y=variables["Y"]!!.integerValue() + evalstack.push(Value(DataType.UWORD, y*256+x)) + } Opcode.POP_VAR_BYTE -> { val value = evalstack.pop() checkDt(value, DataType.UBYTE, DataType.BYTE) diff --git a/prog8lib/c64lib.p8 b/prog8lib/c64lib.p8 index 3d04ca5d7..809b98591 100644 --- a/prog8lib/c64lib.p8 +++ b/prog8lib/c64lib.p8 @@ -120,18 +120,18 @@ asmsub MOVEF () -> clobbers(A,X) -> () = $bc0f ; copy fac1 to fac2 asmsub FTOMEMXY (mflpt: uword @ XY) -> clobbers(A,Y) -> () = $bbd4 ; store fac1 to memory X/Y as 5-byte mflpt ; fac1-> signed word in Y/A (might throw ILLEGAL QUANTITY) -; (use c64flt.FTOSWRDAY to get A/Y output; lo/hi switched to normal order) +; (tip: use c64flt.FTOSWRDAY to get A/Y output; lo/hi switched to normal little endian order) asmsub FTOSWORDYA () -> clobbers(X) -> (ubyte @ Y, ubyte @ A) = $b1aa ; fac1 -> unsigned word in Y/A (might throw ILLEGAL QUANTITY) (result also in $14/15) -; (use c64flt.GETADRAY to get A/Y output; lo/hi switched to normal order) +; (tip: use c64flt.GETADRAY to get A/Y output; lo/hi switched to normal little endian order) asmsub GETADR () -> clobbers(X) -> (ubyte @ Y, ubyte @ A) = $b7f7 asmsub QINT () -> clobbers(A,X,Y) -> () = $bc9b ; fac1 -> 4-byte signed integer in 98-101 ($62-$65), with the MSB FIRST. asmsub AYINT () -> clobbers(A,X,Y) -> () = $b1bf ; fac1-> signed word in 100-101 ($64-$65) MSB FIRST. (might throw ILLEGAL QUANTITY) ; signed word in Y/A -> float in fac1 -; (use c64flt.GIVAYFAY to use A/Y input; lo/hi switched to normal order) +; (tip: use c64flt.GIVAYFAY to use A/Y input; lo/hi switched to normal order) ; there is also c64flt.GIVUAYF - unsigned word in A/Y (lo/hi) to fac1 ; there is also c64flt.FREADS32 that reads from 98-101 ($62-$65) MSB FIRST ; there is also c64flt.FREADUS32 that reads from 98-101 ($62-$65) MSB FIRST @@ -169,7 +169,7 @@ asmsub ABS () -> clobbers() -> () = $bc58 ; fac1 = ABS(fac1) asmsub SQR () -> clobbers(A,X,Y) -> () = $bf71 ; fac1 = SQRT(fac1) asmsub EXP () -> clobbers(A,X,Y) -> () = $bfed ; fac1 = EXP(fac1) (e ** fac1) asmsub NEGOP () -> clobbers(A) -> () = $bfb4 ; switch the sign of fac1 -asmsub RND () -> clobbers(A,X,Y) -> () = $e097 ; fac1 = RND() (use RNDA instead) +asmsub RND () -> clobbers(A,X,Y) -> () = $e097 ; fac1 = RND() (tip: use RNDA instead) asmsub RNDA (acc: ubyte @ A) -> clobbers(A,X,Y) -> () = $e09a ; fac1 = RND(A) asmsub COS () -> clobbers(A,X,Y) -> () = $e264 ; fac1 = COS(fac1) asmsub SIN () -> clobbers(A,X,Y) -> () = $e26b ; fac1 = SIN(fac1) @@ -229,7 +229,7 @@ asmsub CLALL () -> clobbers(A,X) -> () = $FFE7 ; (via 812 ($32C)) close al asmsub UDTIM () -> clobbers(A,X) -> () = $FFEA ; update the software clock asmsub SCREEN () -> clobbers() -> (ubyte @ X, ubyte @ Y) = $FFED ; read number of screen rows and columns asmsub PLOT (dir: ubyte @ Pc, col: ubyte @ Y, row: ubyte @ X) -> clobbers() -> (ubyte @ X, ubyte @ Y) = $FFF0 ; read/set position of cursor on screen -asmsub IOBASE () -> clobbers() -> (ubyte @ X, ubyte @ Y) = $FFF3 ; read base address of I/O devices +asmsub IOBASE () -> clobbers() -> (uword @ XY) = $FFF3 ; read base address of I/O devices ; ---- end of C64 kernal routines ----