diff --git a/codeCore/src/prog8/code/SymbolTable.kt b/codeCore/src/prog8/code/SymbolTable.kt index b2ec2d2cc..6419b3169 100644 --- a/codeCore/src/prog8/code/SymbolTable.kt +++ b/codeCore/src/prog8/code/SymbolTable.kt @@ -259,7 +259,7 @@ class StMemorySlab( StNode(name, StNodeType.MEMORYSLAB, astNode) -class StSub(name: String, val parameters: List, val returnType: DataType?, astNode: PtNode) : +class StSub(name: String, val parameters: List, val returns: List, astNode: PtNode) : StNode(name, StNodeType.SUBROUTINE, astNode) diff --git a/codeCore/src/prog8/code/SymbolTableMaker.kt b/codeCore/src/prog8/code/SymbolTableMaker.kt index a1dd8574f..c6bd870a8 100644 --- a/codeCore/src/prog8/code/SymbolTableMaker.kt +++ b/codeCore/src/prog8/code/SymbolTableMaker.kt @@ -58,7 +58,7 @@ class SymbolTableMaker(private val program: PtProgram, private val options: Comp } is PtSub -> { val params = node.parameters.map {StSubroutineParameter(it.name, it.type, it.register) } - StSub(node.name, params, node.returntype, node) + StSub(node.name, params, node.returns, node) } is PtVariable -> { val initialNumeric: Double? diff --git a/codeCore/src/prog8/code/ast/AstExpressions.kt b/codeCore/src/prog8/code/ast/AstExpressions.kt index bd7f2f900..9c58e9910 100644 --- a/codeCore/src/prog8/code/ast/AstExpressions.kt +++ b/codeCore/src/prog8/code/ast/AstExpressions.kt @@ -242,7 +242,7 @@ class PtFunctionCall(val name: String, if(void) require(type.isUndefined) { "void fcall should have undefined datatype" } - // note: non-void calls can have UNDEFINED type: is if they return more than 1 value + // note: non-void calls can have UNDEFINED type: if they return more than 1 value } } diff --git a/codeCore/src/prog8/code/ast/AstPrinter.kt b/codeCore/src/prog8/code/ast/AstPrinter.kt index 06fdbeddd..7d1035f93 100644 --- a/codeCore/src/prog8/code/ast/AstPrinter.kt +++ b/codeCore/src/prog8/code/ast/AstPrinter.kt @@ -125,8 +125,8 @@ fun printAst(root: PtNode, skipLibraries: Boolean, output: (text: String) -> Uni "${it.type} ${it.name} $reg" } var str = "sub ${node.name}($params) " - if(node.returntype!=null) - str += "-> ${node.returntype}" + if(node.returns.isNotEmpty()) + str += "-> ${node.returns.joinToString(",")}" str } is PtVariable -> { diff --git a/codeCore/src/prog8/code/ast/AstStatements.kt b/codeCore/src/prog8/code/ast/AstStatements.kt index 39b4fd6d9..f7860e790 100644 --- a/codeCore/src/prog8/code/ast/AstStatements.kt +++ b/codeCore/src/prog8/code/ast/AstStatements.kt @@ -25,15 +25,15 @@ class PtAsmSub( class PtSub( name: String, val parameters: List, - val returntype: DataType?, + val returns: List, position: Position ) : PtNamedNode(name, position), IPtSubroutine, IPtStatementContainer { init { - // params and return value should not be str + // params and return values should not be str if(parameters.any{ !it.type.isNumericOrBool }) throw AssemblyError("non-numeric/non-bool parameter") - if(returntype!=null && !returntype.isNumericOrBool) - throw AssemblyError("non-numeric/non-bool returntype $returntype") + if(returns.any { !it.isNumericOrBool }) + throw AssemblyError("non-numeric/non-bool returntype") parameters.forEach { it.parent=this } } } diff --git a/codeGenCpu6502/src/prog8/codegen/cpu6502/AsmGen.kt b/codeGenCpu6502/src/prog8/codegen/cpu6502/AsmGen.kt index be9507af5..0e3631831 100644 --- a/codeGenCpu6502/src/prog8/codegen/cpu6502/AsmGen.kt +++ b/codeGenCpu6502/src/prog8/codegen/cpu6502/AsmGen.kt @@ -1054,20 +1054,20 @@ $repeatLabel""") val returnvalue = ret.children.singleOrNull() if(returnvalue!=null) { val sub = ret.definingSub()!! - val returnReg = sub.returnRegister()!! - if (sub.returntype?.isNumericOrBool==true) { - assignExpressionToRegister(returnvalue as PtExpression, returnReg.registerOrPair!!) + val returnReg = sub.returnsWhatWhere().single() + if (sub.returns.single().isNumericOrBool==true) { + assignExpressionToRegister(returnvalue as PtExpression, returnReg.first.registerOrPair!!) } else { // all else take its address and assign that also to AY register pair val addrofValue = PtAddressOf(returnvalue.position) addrofValue.add(returnvalue as PtIdentifier) addrofValue.parent = ret.parent - assignmentAsmGen.assignExpressionToRegister(addrofValue, returnReg.registerOrPair!!, false) + assignmentAsmGen.assignExpressionToRegister(addrofValue, returnReg.first.registerOrPair!!, false) } } else if(ret.children.size>1) { - TODO("multi-value return") + TODO("multi-value return ; choose call convention: everything on stack?") } out(" rts") } diff --git a/codeGenCpu6502/src/prog8/codegen/cpu6502/Extensions.kt b/codeGenCpu6502/src/prog8/codegen/cpu6502/Extensions.kt index 11e4ad8b1..de3c8bd45 100644 --- a/codeGenCpu6502/src/prog8/codegen/cpu6502/Extensions.kt +++ b/codeGenCpu6502/src/prog8/codegen/cpu6502/Extensions.kt @@ -3,6 +3,7 @@ package prog8.codegen.cpu6502 import prog8.code.ast.IPtSubroutine import prog8.code.ast.PtAsmSub import prog8.code.ast.PtSub +import prog8.code.core.AssemblyError import prog8.code.core.DataType import prog8.code.core.RegisterOrPair import prog8.code.core.RegisterOrStatusflag @@ -14,29 +15,25 @@ internal fun IPtSubroutine.returnsWhatWhere(): List { - // for non-asm subroutines, determine the return registers based on the type of the return value - return if(returntype==null) - emptyList() - else { - val register = when { - returntype!!.isByteOrBool -> RegisterOrStatusflag(RegisterOrPair.A, null) - returntype!!.isWord -> RegisterOrStatusflag(RegisterOrPair.AY, null) - returntype!!.isFloat -> RegisterOrStatusflag(RegisterOrPair.FAC1, null) - else -> RegisterOrStatusflag(RegisterOrPair.AY, null) + // for non-asm subroutines, determine the return registers based on the type of the return values + + when(returns.size) { + 0 -> return emptyList() + 1 -> { + val returntype = returns.single() + val register = when { + returntype.isByteOrBool -> RegisterOrStatusflag(RegisterOrPair.A, null) + returntype.isWord -> RegisterOrStatusflag(RegisterOrPair.AY, null) + returntype.isFloat -> RegisterOrStatusflag(RegisterOrPair.FAC1, null) + else -> RegisterOrStatusflag(RegisterOrPair.AY, null) + } + return listOf(Pair(register, returntype)) + } + else -> { + // TODO for multi-value results, put the first one in register(s) and only the rest elsewhere (like stack)??? + throw AssemblyError("multi-value returns from a normal subroutine are not put into registers, this routine shouldn't have been called in this scenario") } - listOf(Pair(register, returntype!!)) } } } } - - -internal fun PtSub.returnRegister(): RegisterOrStatusflag? { - return when { - returntype?.isByteOrBool==true -> RegisterOrStatusflag(RegisterOrPair.A, null) - returntype?.isWord==true -> RegisterOrStatusflag(RegisterOrPair.AY, null) - returntype?.isFloat==true -> RegisterOrStatusflag(RegisterOrPair.FAC1, null) - returntype==null -> null - else -> RegisterOrStatusflag(RegisterOrPair.AY, null) - } -} diff --git a/codeGenCpu6502/src/prog8/codegen/cpu6502/assignment/AsmAssignment.kt b/codeGenCpu6502/src/prog8/codegen/cpu6502/assignment/AsmAssignment.kt index 5ab4318c6..c741ead61 100644 --- a/codeGenCpu6502/src/prog8/codegen/cpu6502/assignment/AsmAssignment.kt +++ b/codeGenCpu6502/src/prog8/codegen/cpu6502/assignment/AsmAssignment.kt @@ -194,9 +194,12 @@ internal class AsmAssignSource(val kind: SourceStorageKind, is PtFunctionCall -> { val symbol = asmgen.symbolTable.lookup(value.name) ?: throw AssemblyError("lookup error ${value.name}") val sub = symbol.astNode as IPtSubroutine - val returnType = sub.returnsWhatWhere().firstOrNull { rr -> rr.first.registerOrPair != null || rr.first.statusflag!=null }?.second + val returnType = + if(sub is PtSub && sub.returns.size>1) + DataType.forDt(BaseDataType.UNDEFINED) // TODO list of types instead? + else + sub.returnsWhatWhere().firstOrNull { rr -> rr.first.registerOrPair != null || rr.first.statusflag!=null }?.second ?: throw AssemblyError("can't translate zero return values in assignment") - AsmAssignSource(SourceStorageKind.EXPRESSION, program, asmgen, returnType, expression = value) } else -> { diff --git a/codeGenCpu6502/src/prog8/codegen/cpu6502/assignment/AssignmentAsmGen.kt b/codeGenCpu6502/src/prog8/codegen/cpu6502/assignment/AssignmentAsmGen.kt index ca1efe406..4ee97f669 100644 --- a/codeGenCpu6502/src/prog8/codegen/cpu6502/assignment/AssignmentAsmGen.kt +++ b/codeGenCpu6502/src/prog8/codegen/cpu6502/assignment/AssignmentAsmGen.kt @@ -4,6 +4,7 @@ import prog8.code.StMemVar import prog8.code.StExtSub import prog8.code.StExtSubParameter import prog8.code.StStaticVariable +import prog8.code.StSub import prog8.code.ast.* import prog8.code.core.* import prog8.codegen.cpu6502.AsmGen6502Internal @@ -39,27 +40,35 @@ internal class AssignmentAsmGen( val values = assignment.value as? PtFunctionCall ?: throw AssemblyError("only function calls can return multiple values in a multi-assign") - val sub = asmgen.symbolTable.lookup(values.name) as? StExtSub - ?: throw AssemblyError("only asmsubs can return multiple values") + // TODO use assignExpression() for all of this ?? - require(sub.returns.size>=2) - if(sub.returns.any { it.type.isFloat }) - TODO("deal with (multiple?) FP return registers") + val extsub = asmgen.symbolTable.lookup(values.name) as? StExtSub + if(extsub!=null) { + require(extsub.returns.size>=2) + if(extsub.returns.any { it.type.isFloat }) + TODO("deal with (multiple?) FP return registers") - asmgen.translate(values) + asmgen.translate(values) - val assignmentTargets = assignment.children.dropLast(1) - if(sub.returns.size==assignmentTargets.size) { - // because we can only handle integer results right now we can just zip() it all up - val (statusFlagResults, registersResults) = sub.returns.zip(assignmentTargets).partition { it.first.register.statusflag!=null } - if (statusFlagResults.isEmpty()) - assignRegisterResults(registersResults) - else if(registersResults.isEmpty()) - assignOnlyTheStatusFlagsResults(false, statusFlagResults) - else - assignStatusFlagsAndRegistersResults(statusFlagResults, registersResults) + val assignmentTargets = assignment.children.dropLast(1) + if(extsub.returns.size==assignmentTargets.size) { + // because we can only handle integer results right now we can just zip() it all up + val (statusFlagResults, registersResults) = extsub.returns.zip(assignmentTargets).partition { it.first.register.statusflag!=null } + if (statusFlagResults.isEmpty()) + assignRegisterResults(registersResults) + else if(registersResults.isEmpty()) + assignOnlyTheStatusFlagsResults(false, statusFlagResults) + else + assignStatusFlagsAndRegistersResults(statusFlagResults, registersResults) + } else { + throw AssemblyError("number of values and targets don't match") + } } else { - throw AssemblyError("number of values and targets don't match") + val sub = asmgen.symbolTable.lookup(values.name) as? StSub + if(sub!=null) { + TODO("multi-value returns ; asignment") + } + else throw AssemblyError("expected extsub or normal sub") } } @@ -452,56 +461,60 @@ internal class AssignmentAsmGen( val symbol = asmgen.symbolTable.lookup(value.name) val sub = symbol!!.astNode as IPtSubroutine asmgen.translateFunctionCall(value) - val returnValue = sub.returnsWhatWhere().singleOrNull { it.first.registerOrPair!=null } ?: sub.returnsWhatWhere().single { it.first.statusflag!=null } - when { - returnValue.second.isString -> { - val targetDt = assign.target.datatype - when { - targetDt.isUnsignedWord -> { - // assign the address of the string result value - assignRegisterpairWord(assign.target, RegisterOrPair.AY) + if(sub is PtSub && sub.returns.size>1) { + TODO("multi-value returns ; handle functioncall result") + } else { + val returnValue = sub.returnsWhatWhere().singleOrNull { it.first.registerOrPair!=null } ?: sub.returnsWhatWhere().single { it.first.statusflag!=null } + when { + returnValue.second.isString -> { + val targetDt = assign.target.datatype + when { + targetDt.isUnsignedWord -> { + // assign the address of the string result value + assignRegisterpairWord(assign.target, RegisterOrPair.AY) + } + targetDt.isString || targetDt.isUnsignedByteArray || targetDt.isByteArray -> { + throw AssemblyError("stringvalue assignment should have been replaced by a call to strcpy") + } + else -> throw AssemblyError("weird target dt") } - targetDt.isString || targetDt.isUnsignedByteArray || targetDt.isByteArray -> { - throw AssemblyError("stringvalue assignment should have been replaced by a call to strcpy") - } - else -> throw AssemblyError("weird target dt") } - } - returnValue.second.isFloat -> { - // float result from function sits in FAC1 - assignFAC1float(assign.target) - } - else -> { - // do NOT restore X register before assigning the result values first - when (returnValue.first.registerOrPair) { - RegisterOrPair.A -> assignRegisterByte(assign.target, CpuRegister.A, returnValue.second.isSigned, true) - RegisterOrPair.X -> assignRegisterByte(assign.target, CpuRegister.X, returnValue.second.isSigned, true) - RegisterOrPair.Y -> assignRegisterByte(assign.target, CpuRegister.Y, returnValue.second.isSigned, true) - RegisterOrPair.AX -> assignVirtualRegister(assign.target, RegisterOrPair.AX) - RegisterOrPair.AY -> assignVirtualRegister(assign.target, RegisterOrPair.AY) - RegisterOrPair.XY -> assignVirtualRegister(assign.target, RegisterOrPair.XY) - RegisterOrPair.R0 -> assignVirtualRegister(assign.target, RegisterOrPair.R0) - RegisterOrPair.R1 -> assignVirtualRegister(assign.target, RegisterOrPair.R1) - RegisterOrPair.R2 -> assignVirtualRegister(assign.target, RegisterOrPair.R2) - RegisterOrPair.R3 -> assignVirtualRegister(assign.target, RegisterOrPair.R3) - RegisterOrPair.R4 -> assignVirtualRegister(assign.target, RegisterOrPair.R4) - RegisterOrPair.R5 -> assignVirtualRegister(assign.target, RegisterOrPair.R5) - RegisterOrPair.R6 -> assignVirtualRegister(assign.target, RegisterOrPair.R6) - RegisterOrPair.R7 -> assignVirtualRegister(assign.target, RegisterOrPair.R7) - RegisterOrPair.R8 -> assignVirtualRegister(assign.target, RegisterOrPair.R8) - RegisterOrPair.R9 -> assignVirtualRegister(assign.target, RegisterOrPair.R9) - RegisterOrPair.R10 -> assignVirtualRegister(assign.target, RegisterOrPair.R10) - RegisterOrPair.R11 -> assignVirtualRegister(assign.target, RegisterOrPair.R11) - RegisterOrPair.R12 -> assignVirtualRegister(assign.target, RegisterOrPair.R12) - RegisterOrPair.R13 -> assignVirtualRegister(assign.target, RegisterOrPair.R13) - RegisterOrPair.R14 -> assignVirtualRegister(assign.target, RegisterOrPair.R14) - RegisterOrPair.R15 -> assignVirtualRegister(assign.target, RegisterOrPair.R15) - else -> { - val sflag = returnValue.first.statusflag - if(sflag!=null) - assignStatusFlagByte(assign.target, sflag) - else - throw AssemblyError("should be just one register byte result value") + returnValue.second.isFloat -> { + // float result from function sits in FAC1 + assignFAC1float(assign.target) + } + else -> { + // do NOT restore X register before assigning the result values first + when (returnValue.first.registerOrPair) { + RegisterOrPair.A -> assignRegisterByte(assign.target, CpuRegister.A, returnValue.second.isSigned, true) + RegisterOrPair.X -> assignRegisterByte(assign.target, CpuRegister.X, returnValue.second.isSigned, true) + RegisterOrPair.Y -> assignRegisterByte(assign.target, CpuRegister.Y, returnValue.second.isSigned, true) + RegisterOrPair.AX -> assignVirtualRegister(assign.target, RegisterOrPair.AX) + RegisterOrPair.AY -> assignVirtualRegister(assign.target, RegisterOrPair.AY) + RegisterOrPair.XY -> assignVirtualRegister(assign.target, RegisterOrPair.XY) + RegisterOrPair.R0 -> assignVirtualRegister(assign.target, RegisterOrPair.R0) + RegisterOrPair.R1 -> assignVirtualRegister(assign.target, RegisterOrPair.R1) + RegisterOrPair.R2 -> assignVirtualRegister(assign.target, RegisterOrPair.R2) + RegisterOrPair.R3 -> assignVirtualRegister(assign.target, RegisterOrPair.R3) + RegisterOrPair.R4 -> assignVirtualRegister(assign.target, RegisterOrPair.R4) + RegisterOrPair.R5 -> assignVirtualRegister(assign.target, RegisterOrPair.R5) + RegisterOrPair.R6 -> assignVirtualRegister(assign.target, RegisterOrPair.R6) + RegisterOrPair.R7 -> assignVirtualRegister(assign.target, RegisterOrPair.R7) + RegisterOrPair.R8 -> assignVirtualRegister(assign.target, RegisterOrPair.R8) + RegisterOrPair.R9 -> assignVirtualRegister(assign.target, RegisterOrPair.R9) + RegisterOrPair.R10 -> assignVirtualRegister(assign.target, RegisterOrPair.R10) + RegisterOrPair.R11 -> assignVirtualRegister(assign.target, RegisterOrPair.R11) + RegisterOrPair.R12 -> assignVirtualRegister(assign.target, RegisterOrPair.R12) + RegisterOrPair.R13 -> assignVirtualRegister(assign.target, RegisterOrPair.R13) + RegisterOrPair.R14 -> assignVirtualRegister(assign.target, RegisterOrPair.R14) + RegisterOrPair.R15 -> assignVirtualRegister(assign.target, RegisterOrPair.R15) + else -> { + val sflag = returnValue.first.statusflag + if(sflag!=null) + assignStatusFlagByte(assign.target, sflag) + else + throw AssemblyError("should be just one register byte result value") + } } } } diff --git a/codeGenCpu6502/test/TestCodegen.kt b/codeGenCpu6502/test/TestCodegen.kt index bafe3f760..65ff5b89e 100644 --- a/codeGenCpu6502/test/TestCodegen.kt +++ b/codeGenCpu6502/test/TestCodegen.kt @@ -48,7 +48,7 @@ class TestCodegen: FunSpec({ val codegen = AsmGen6502(prefixSymbols = false, 0) val program = PtProgram("test", DummyMemsizer, DummyStringEncoder) val block = PtBlock("main",false, SourceCode.Generated("test"), PtBlock.Options(), Position.DUMMY) - val sub = PtSub("start", emptyList(), null, Position.DUMMY) + val sub = PtSub("start", emptyList(), emptyList(), Position.DUMMY) sub.add(PtVariable( "pi", DataType.forDt(BaseDataType.UBYTE), diff --git a/codeGenIntermediate/src/prog8/codegen/intermediate/AssignmentGen.kt b/codeGenIntermediate/src/prog8/codegen/intermediate/AssignmentGen.kt index b84c0d5dc..459a1fb6a 100644 --- a/codeGenIntermediate/src/prog8/codegen/intermediate/AssignmentGen.kt +++ b/codeGenIntermediate/src/prog8/codegen/intermediate/AssignmentGen.kt @@ -2,6 +2,7 @@ package prog8.codegen.intermediate import prog8.code.StExtSub import prog8.code.StExtSubParameter +import prog8.code.StSub import prog8.code.ast.* import prog8.code.core.* import prog8.intermediate.* @@ -14,29 +15,37 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val express val values = assignment.value as? PtFunctionCall ?: throw AssemblyError("only function calls can return multiple values in a multi-assign") - val sub = codeGen.symbolTable.lookup(values.name) as? StExtSub - ?: throw AssemblyError("only asmsubs can return multiple values") - val result = mutableListOf() - val funcCall = this.expressionEval.translate(values) + val funcCall = expressionEval.translate(values) require(funcCall.multipleResultRegs.size + funcCall.multipleResultFpRegs.size >= 2) - if(funcCall.multipleResultFpRegs.isNotEmpty()) + if (funcCall.multipleResultFpRegs.isNotEmpty()) TODO("deal with (multiple?) FP return registers") val assignmentTargets = assignment.children.dropLast(1) addToResult(result, funcCall, funcCall.resultReg, funcCall.resultFpReg) - if(sub.returns.size==assignmentTargets.size) { - // Targets and values match. Assign all the things. Skip 'void' targets. - sub.returns.zip(assignmentTargets).zip(funcCall.multipleResultRegs).forEach { - val target = it.first.second as PtAssignTarget - if(!target.void) { - val regNumber = it.second - val returns = it.first.first - result += assignCpuRegister(returns, regNumber, target) + + val extsub = codeGen.symbolTable.lookup(values.name) as? StExtSub + if(extsub!=null) { + if (extsub.returns.size == assignmentTargets.size) { + // Targets and values match. Assign all the things. Skip 'void' targets. + extsub.returns.zip(assignmentTargets).zip(funcCall.multipleResultRegs).forEach { + val target = it.first.second as PtAssignTarget + if (!target.void) { + val regNumber = it.second + val returns = it.first.first + result += assignCpuRegister(returns, regNumber, target) + } } + } else { + throw AssemblyError("number of values and targets don't match") } } else { - throw AssemblyError("number of values and targets don't match") + val normalsub = codeGen.symbolTable.lookup(values.name) as? StSub + if (normalsub != null) { + TODO() + } + else throw AssemblyError("expected extsub or normal sub") } + return result } else { if (assignment.target.children.single() is PtIrRegister) diff --git a/codeGenIntermediate/src/prog8/codegen/intermediate/ExpressionGen.kt b/codeGenIntermediate/src/prog8/codegen/intermediate/ExpressionGen.kt index 74d3932fa..508dd89f0 100644 --- a/codeGenIntermediate/src/prog8/codegen/intermediate/ExpressionGen.kt +++ b/codeGenIntermediate/src/prog8/codegen/intermediate/ExpressionGen.kt @@ -634,20 +634,27 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) { result += codeGen.translateNode(assign) } } - // return value (always singular for normal Subs) - val returnRegSpec = if(fcall.void) null else { - val returnIrType = irType(callTarget.returnType!!) - FunctionCallArgs.RegSpec(returnIrType, codeGen.registers.next(returnIrType), null) + // return value(s) + val returnRegSpecs = if(fcall.void) emptyList() else { + callTarget.returns.map { + val returnIrType = irType(it) + FunctionCallArgs.RegSpec(returnIrType, codeGen.registers.next(returnIrType), null) + } } // create the call addInstr(result, IRInstruction(Opcode.CALL, labelSymbol = fcall.name, - fcallArgs = FunctionCallArgs(argRegisters, if(returnRegSpec==null) emptyList() else listOf(returnRegSpec))), null) + fcallArgs = FunctionCallArgs(argRegisters, returnRegSpecs)), null) return if(fcall.void) - ExpressionCodeResult(result, IRDataType.BYTE, -1, -1) - else if(fcall.type.isFloat) - ExpressionCodeResult(result, returnRegSpec!!.dt, -1, returnRegSpec.registerNum) - else - ExpressionCodeResult(result, returnRegSpec!!.dt, returnRegSpec.registerNum, -1) + ExpressionCodeResult(result, IRDataType.BYTE, -1, -1) // TODO void? + else if(returnRegSpecs.size==1) { + val returnRegSpec = returnRegSpecs.single() + if (fcall.type.isFloat) + ExpressionCodeResult(result, returnRegSpec.dt, -1, returnRegSpec.registerNum) + else + ExpressionCodeResult(result, returnRegSpec.dt, returnRegSpec.registerNum, -1) + } else { + TODO("multi-value return ; expression result") + } } is StExtSub -> { val result = mutableListOf() diff --git a/codeGenIntermediate/src/prog8/codegen/intermediate/IRCodeGen.kt b/codeGenIntermediate/src/prog8/codegen/intermediate/IRCodeGen.kt index 142d52c5e..5e1715434 100644 --- a/codeGenIntermediate/src/prog8/codegen/intermediate/IRCodeGen.kt +++ b/codeGenIntermediate/src/prog8/codegen/intermediate/IRCodeGen.kt @@ -1804,7 +1804,7 @@ class IRCodeGen( is PtVariable, is PtConstant, is PtMemMapped -> { /* vars should be looked up via symbol table */ } is PtAlign -> TODO("ir support for inline %align") is PtSub -> { - val sub = IRSubroutine(child.name, translate(child.parameters), child.returntype, child.position) + val sub = IRSubroutine(child.name, translate(child.parameters), child.returns, child.position) for (subchild in child.children) { translateNode(subchild).forEach { sub += it } } diff --git a/codeGenIntermediate/test/TestIRPeepholeOpt.kt b/codeGenIntermediate/test/TestIRPeepholeOpt.kt index 77debf444..0763b8c66 100644 --- a/codeGenIntermediate/test/TestIRPeepholeOpt.kt +++ b/codeGenIntermediate/test/TestIRPeepholeOpt.kt @@ -9,7 +9,7 @@ class TestIRPeepholeOpt: FunSpec({ fun makeIRProgram(chunks: List): IRProgram { require(chunks.first().label=="main.start") val block = IRBlock("main", false, IRBlock.Options(), Position.DUMMY) - val sub = IRSubroutine("main.start", emptyList(), null, Position.DUMMY) + val sub = IRSubroutine("main.start", emptyList(), emptyList(), Position.DUMMY) chunks.forEach { sub += it } block += sub val target = VMTarget() diff --git a/codeGenIntermediate/test/TestVmCodeGen.kt b/codeGenIntermediate/test/TestVmCodeGen.kt index 3a19e49b7..00d55edbc 100644 --- a/codeGenIntermediate/test/TestVmCodeGen.kt +++ b/codeGenIntermediate/test/TestVmCodeGen.kt @@ -45,7 +45,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 sub = PtSub("start", emptyList(), null, Position.DUMMY) + val sub = PtSub("start", emptyList(), emptyList(), Position.DUMMY) sub.add(PtVariable( "pi", DataType.forDt(BaseDataType.UBYTE), @@ -160,7 +160,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 sub = PtSub("start", emptyList(), null, Position.DUMMY) + val sub = PtSub("start", emptyList(), emptyList(), Position.DUMMY) sub.add(PtVariable( "f1", DataType.forDt(BaseDataType.FLOAT), @@ -231,7 +231,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 sub = PtSub("start", emptyList(), null, Position.DUMMY) + val sub = PtSub("start", emptyList(), emptyList(), Position.DUMMY) sub.add(PtVariable( "f1", DataType.forDt(BaseDataType.FLOAT), @@ -298,7 +298,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 sub = PtSub("start", emptyList(), null, Position.DUMMY) + val sub = PtSub("start", emptyList(), emptyList(), Position.DUMMY) sub.add(PtVariable( "f1", DataType.forDt(BaseDataType.FLOAT), @@ -353,7 +353,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 sub = PtSub("start", emptyList(), null, Position.DUMMY) + val sub = PtSub("start", emptyList(), emptyList(), Position.DUMMY) sub.add(PtVariable( "sb1", DataType.forDt(BaseDataType.BYTE), @@ -424,7 +424,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 sub = PtSub("start", emptyList(), null, Position.DUMMY) + val sub = PtSub("start", emptyList(), emptyList(), Position.DUMMY) sub.add(PtVariable( "sb1", DataType.forDt(BaseDataType.BYTE), @@ -491,7 +491,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 sub = PtSub("start", emptyList(), null, Position.DUMMY) + val sub = PtSub("start", emptyList(), emptyList(), Position.DUMMY) sub.add(PtVariable( "ub1", DataType.forDt(BaseDataType.BYTE), @@ -541,7 +541,7 @@ class TestVmCodeGen: FunSpec({ val block = PtBlock("main", false, SourceCode.Generated("test"), PtBlock.Options(), Position.DUMMY) val extsub = PtAsmSub("routine", PtAsmSub.Address(null, null, 0x5000u), setOf(CpuRegister.Y), emptyList(), emptyList(), false, Position.DUMMY) block.add(extsub) - val sub = PtSub("start", emptyList(), null, Position.DUMMY) + val sub = PtSub("start", emptyList(), emptyList(), Position.DUMMY) val call = PtFunctionCall("main.routine", true, DataType.forDt(BaseDataType.UNDEFINED), Position.DUMMY) sub.add(call) block.add(sub) diff --git a/compiler/src/prog8/compiler/astprocessing/AstChecker.kt b/compiler/src/prog8/compiler/astprocessing/AstChecker.kt index ef5d25d61..8ca39e3b7 100644 --- a/compiler/src/prog8/compiler/astprocessing/AstChecker.kt +++ b/compiler/src/prog8/compiler/astprocessing/AstChecker.kt @@ -118,10 +118,6 @@ internal class AstChecker(private val program: Program, override fun visit(returnStmt: Return) { val expectedReturnValues = returnStmt.definingSubroutine?.returntypes ?: emptyList() - if(expectedReturnValues.size>1) { - throw FatalAstException("cannot use a return with one value in a subroutine that has multiple return values: $returnStmt") - } - if(returnStmt.values.size1) - err("subroutines can only have one return value") - // subroutine must contain at least one 'return' or 'goto' // (or if it has an asm block, that must contain a 'rts' or 'jmp' or 'bra') if(!hasReturnOrExternalJumpOrRts(subroutine)) { diff --git a/compiler/src/prog8/compiler/astprocessing/IntermediateAstMaker.kt b/compiler/src/prog8/compiler/astprocessing/IntermediateAstMaker.kt index fb7d1b8b0..839610beb 100644 --- a/compiler/src/prog8/compiler/astprocessing/IntermediateAstMaker.kt +++ b/compiler/src/prog8/compiler/astprocessing/IntermediateAstMaker.kt @@ -4,8 +4,8 @@ import com.github.michaelbull.result.Ok import com.github.michaelbull.result.Result import com.github.michaelbull.result.getOrElse import com.github.michaelbull.result.mapError -import prog8.ast.Program import prog8.ast.FatalAstException +import prog8.ast.Program import prog8.ast.expressions.* import prog8.ast.statements.* import prog8.code.ast.* @@ -535,20 +535,19 @@ class IntermediateAstMaker(private val program: Program, private val errors: IEr private fun transformSub(srcSub: Subroutine): PtSub { val (vardecls, statements) = srcSub.statements.partition { it is VarDecl } - var returntype = srcSub.returntypes.singleOrNull() - if(returntype?.isString==true) - returntype=DataType.forDt(BaseDataType.UWORD) // if a sub returns 'str', replace with uword. Intermediate AST and I.R. don't contain 'str' datatype anymore. - + // if a sub returns 'str', replace with uword. Intermediate AST and I.R. don't contain 'str' datatype anymore. + var returnTypes = srcSub.returntypes.map { + if(it.isString) DataType.forDt(BaseDataType.UWORD) else it + } // do not bother about the 'inline' hint of the source subroutine. val sub = PtSub(srcSub.name, srcSub.parameters.map { PtSubroutineParameter(it.name, it.type, it.registerOrPair, it.position) }, - returntype, + returnTypes, srcSub.position) sub.parameters.forEach { it.parent=sub } makeScopeVarsDecls(vardecls).forEach { sub.add(it) } for (statement in statements) sub.add(transformStatement(statement)) - return sub } diff --git a/compiler/src/prog8/compiler/astprocessing/IntermediateAstPostprocess.kt b/compiler/src/prog8/compiler/astprocessing/IntermediateAstPostprocess.kt index f15eb3d29..4d1c3d51a 100644 --- a/compiler/src/prog8/compiler/astprocessing/IntermediateAstPostprocess.kt +++ b/compiler/src/prog8/compiler/astprocessing/IntermediateAstPostprocess.kt @@ -13,7 +13,7 @@ internal fun postprocessIntermediateAst(program: PtProgram, st: SymbolTable, err private fun processDefers(program: PtProgram, st: SymbolTable, errors: IErrorReporter) { val defers = setDeferMasks(program, errors) if(errors.noErrors()) - integrateDefers(defers, program, st) + integrateDefers(defers, program, st, errors) } private const val maskVarName = "prog8_defers_mask" @@ -77,7 +77,7 @@ private fun setDeferMasks(program: PtProgram, errors: IErrorReporter): Map>, program: PtProgram, st: SymbolTable) { +private fun integrateDefers(subdefers: Map>, program: PtProgram, st: SymbolTable, errors: IErrorReporter) { val jumpsAndCallsToAugment = mutableListOf() val returnsToAugment = mutableListOf() val subEndsToAugment = mutableListOf() @@ -149,15 +149,14 @@ private fun integrateDefers(subdefers: Map>, program: PtPro } // complex return value, need to store it before calling the defer block - if(ret.children.size>1) { - TODO("multi-value return ; defer") - } - - val (pushCall, popCall) = makePushPopFunctionCalls(ret.children[0] as PtExpression) + errors.warn("using defer with complex return value(s) incurs stack overhead", ret.children.first { !notComplex(it as PtExpression)}.position) + val pushAndPopCalls = ret.children.map { makePushPopFunctionCalls(it as PtExpression) } + val pushCalls = pushAndPopCalls.map { it.first }.reversed() // push in reverse order + val popCalls = pushAndPopCalls.map { it.second } val newRet = PtReturn(ret.position) - newRet.add(popCall) val group = PtNodeGroup() - group.add(pushCall) + pushCalls.forEach { group.add(it) } + popCalls.forEach { newRet.add(it) } group.add(PtFunctionCall(ret.definingSub()!!.scopedName+"."+invokeDefersRoutineName, true,DataType.forDt(BaseDataType.UNDEFINED), ret.position)) group.add(newRet) group.parent = ret.parent @@ -180,7 +179,7 @@ private fun integrateDefers(subdefers: Map>, program: PtPro for( (sub, defers) in subdefers) { // create the routine that calls the enabled defers in reverse order - val defersRoutine = PtSub(invokeDefersRoutineName, emptyList(), null, Position.DUMMY) + val defersRoutine = PtSub(invokeDefersRoutineName, emptyList(), emptyList(), Position.DUMMY) defersRoutine.parent=sub for((idx, defer) in defers.reversed().withIndex()) { val shift = PtAugmentedAssign(">>=", Position.DUMMY) diff --git a/compiler/src/prog8/compiler/astprocessing/TypecastsAdder.kt b/compiler/src/prog8/compiler/astprocessing/TypecastsAdder.kt index 216042332..424b99b24 100644 --- a/compiler/src/prog8/compiler/astprocessing/TypecastsAdder.kt +++ b/compiler/src/prog8/compiler/astprocessing/TypecastsAdder.kt @@ -333,40 +333,31 @@ class TypecastsAdder(val program: Program, val options: CompilationOptions, val if (subroutine.returntypes.size != returnStmt.values.size) return noModifications + val modifications = mutableListOf() for((index, pair) in returnStmt.values.zip(subroutine.returntypes).withIndex()) { val (returnValue, subReturnType) = pair - println("$index $returnValue -> $subReturnType") - } - - // 1 or more return values to check. - val returnValue = returnStmt.values.singleOrNull() - if(returnValue!=null) { - val subReturnType = subroutine.returntypes.single() val returnDt = returnValue.inferType(program) if(!(returnDt istype subReturnType) && returnValue is NumericLiteral) { // see if we might change the returnvalue into the expected type val castedValue = returnValue.convertTypeKeepValue(subReturnType.base) if(castedValue.isValid) { - return listOf(IAstModification.ReplaceNode(returnValue, castedValue.valueOrZero(), returnStmt)) + modifications += listOf(IAstModification.ReplaceNode(returnValue, castedValue.valueOrZero(), returnStmt)) + continue } } if (returnDt istype subReturnType or returnDt.isNotAssignableTo(subReturnType)) - return noModifications + continue if (returnValue is NumericLiteral) { val cast = returnValue.cast(subReturnType.base, true) if(cast.isValid) { - returnStmt.values[0] = cast.valueOrZero() + returnStmt.values[index] = cast.valueOrZero() } } else { - val modifications = mutableListOf() addTypecastOrCastedValueModification(modifications, returnValue, subReturnType.base, returnStmt) - return modifications + continue } } - else if(returnStmt.values.size>1) { - TODO("multi-value return ; typecast") - } - return noModifications + return modifications } override fun after(whenChoice: WhenChoice, parent: Node): Iterable { diff --git a/compiler/test/TestSymbolTable.kt b/compiler/test/TestSymbolTable.kt index 6cd29b18e..efd2a7ea7 100644 --- a/compiler/test/TestSymbolTable.kt +++ b/compiler/test/TestSymbolTable.kt @@ -115,8 +115,8 @@ private fun makeSt(): SymbolTable { val astConstant2 = PtConstant("blockc", DataType.forDt(BaseDataType.UWORD), 999.0, Position.DUMMY) astBlock1.add(astConstant1) astBlock1.add(astConstant2) - val astSub1 = PtSub("sub1", emptyList(), null, Position.DUMMY) - val astSub2 = PtSub("sub2", emptyList(), null, Position.DUMMY) + val astSub1 = PtSub("sub1", emptyList(), emptyList(), Position.DUMMY) + val astSub2 = PtSub("sub2", emptyList(), emptyList(), Position.DUMMY) val astSub1v1 = PtVariable( "v1", DataType.forDt(BaseDataType.BYTE), @@ -182,9 +182,9 @@ private fun makeSt(): SymbolTable { val astBfunc = PtIdentifier("msb", DataType.forDt(BaseDataType.UBYTE), Position.DUMMY) astBlock1.add(astBfunc) val astBlock2 = PtBlock("block2", false, SourceCode.Generated("block2"), PtBlock.Options(), Position.DUMMY) - val astSub21 = PtSub("sub1", emptyList(), null, Position.DUMMY) - val astSub22 = PtSub("sub2", emptyList(), null, Position.DUMMY) - val astSub221 = PtSub("subsub", emptyList(), null, Position.DUMMY) + val astSub21 = PtSub("sub1", emptyList(), emptyList(), Position.DUMMY) + val astSub22 = PtSub("sub2", emptyList(), emptyList(), Position.DUMMY) + val astSub221 = PtSub("subsub", emptyList(), emptyList(), Position.DUMMY) val astLabel = PtLabel("label", Position.DUMMY) astSub221.add(astLabel) astSub22.add(astSub221) diff --git a/compiler/test/TestTypecasts.kt b/compiler/test/TestTypecasts.kt index 823e1c281..51193c048 100644 --- a/compiler/test/TestTypecasts.kt +++ b/compiler/test/TestTypecasts.kt @@ -802,7 +802,7 @@ main { val result = compileText(VMTarget(), true, src, writeAssembly = true)!! val main = result.codegenAst!!.allBlocks().first() val derp = main.children.single { it is PtSub && it.name=="main.derp"} as PtSub - derp.returntype shouldBe DataType.forDt(BaseDataType.UWORD) + derp.returns shouldBe listOf(DataType.forDt(BaseDataType.UWORD)) derp.parameters.single().type shouldBe DataType.forDt(BaseDataType.UWORD) val mult3 = main.children.single { it is PtAsmSub && it.name=="main.mult3"} as PtAsmSub mult3.parameters.single().second.type shouldBe DataType.forDt(BaseDataType.UWORD) diff --git a/docs/source/todo.rst b/docs/source/todo.rst index 94c870816..87c0510f6 100644 --- a/docs/source/todo.rst +++ b/docs/source/todo.rst @@ -1,7 +1,9 @@ TODO ==== -- implement the TODO multi-value return occurences. +- implement the TODO("multi-value occurences in both codegens, to handle multi-value subroutine return values + +- rename "intermediate AST" into "simplified AST" (docs + classes in code) - add paypal donation button as well? - announce prog8 on the 6502.org site? diff --git a/examples/test.p8 b/examples/test.p8 index 4a8d31467..8b0dbfb74 100644 --- a/examples/test.p8 +++ b/examples/test.p8 @@ -3,8 +3,10 @@ main { sub start() { - cx16.r0,cx16.r1 = single() - cx16.r0 = multi() + cx16.r0 = single() + void multi() + cx16.r0,void = multi() + cx16.r0,cx16.r1 = multi() } sub single() -> uword { @@ -12,6 +14,7 @@ main { } sub multi() -> uword, uword { + defer cx16.r0++ return 42+cx16.r0L, 99 } } diff --git a/intermediate/src/prog8/intermediate/IRFileReader.kt b/intermediate/src/prog8/intermediate/IRFileReader.kt index a454a2734..729f78289 100644 --- a/intermediate/src/prog8/intermediate/IRFileReader.kt +++ b/intermediate/src/prog8/intermediate/IRFileReader.kt @@ -428,11 +428,11 @@ class IRFileReader { val start = reader.nextEvent().asStartElement() require(start.name.localPart=="SUB") { "missing SUB" } val attrs = start.attributes.asSequence().associate { it.name.localPart to it.value } - val returntype = attrs.getValue("RETURNTYPE") + val returns = attrs.getValue("RETURNS") skipText(reader) val sub = IRSubroutine(attrs.getValue("NAME"), parseParameters(reader), - if(returntype=="") null else parseDatatype(returntype, false), + if(returns=="") emptyList() else returns.split(',').map { parseDatatype(it, false) }, parsePosition(attrs.getValue("POS"))) skipText(reader) diff --git a/intermediate/src/prog8/intermediate/IRFileWriter.kt b/intermediate/src/prog8/intermediate/IRFileWriter.kt index 51b04724b..02b889679 100644 --- a/intermediate/src/prog8/intermediate/IRFileWriter.kt +++ b/intermediate/src/prog8/intermediate/IRFileWriter.kt @@ -101,7 +101,7 @@ class IRFileWriter(private val irProgram: IRProgram, outfileOverride: Path?) { is IRSubroutine -> { xml.writeStartElement("SUB") xml.writeAttribute("NAME", child.label) - xml.writeAttribute("RETURNTYPE", child.returnType?.irTypeString(null)?.lowercase() ?: "") + xml.writeAttribute("RETURNS", child.returns.joinToString(",") { it.irTypeString(null).lowercase() }) xml.writeAttribute("POS", child.position.toString()) xml.writeCharacters("\n") xml.writeStartElement("PARAMS") diff --git a/intermediate/src/prog8/intermediate/IRProgram.kt b/intermediate/src/prog8/intermediate/IRProgram.kt index cddaa5ecd..41024ad30 100644 --- a/intermediate/src/prog8/intermediate/IRProgram.kt +++ b/intermediate/src/prog8/intermediate/IRProgram.kt @@ -427,7 +427,7 @@ sealed interface IIRBlockElement { class IRSubroutine( override val label: String, val parameters: List, - val returnType: DataType?, + val returns: List, val position: Position): IIRBlockElement { class IRParam(val name: String, val dt: DataType) @@ -440,7 +440,7 @@ class IRSubroutine( // params and return value should not be str require(parameters.all{ it.dt.isNumericOrBool }) {"non-numeric/non-bool parameter"} - require(returnType==null || returnType.isNumericOrBool) {"non-numeric/non-bool returntype $returnType"} + require(returns.all { it.isNumericOrBool}) {"non-numeric/non-bool returntype"} } operator fun plusAssign(chunk: IRCodeChunkBase) { diff --git a/intermediate/test/TestIRFileInOut.kt b/intermediate/test/TestIRFileInOut.kt index 4695409fe..8640a4f72 100644 --- a/intermediate/test/TestIRFileInOut.kt +++ b/intermediate/test/TestIRFileInOut.kt @@ -76,7 +76,7 @@ load.b r1,42 - + dummy @@ -86,7 +86,7 @@ return - + uword sys.wait.jiffies diff --git a/virtualmachine/test/TestVm.kt b/virtualmachine/test/TestVm.kt index c596d0089..825112f8c 100644 --- a/virtualmachine/test/TestVm.kt +++ b/virtualmachine/test/TestVm.kt @@ -46,7 +46,7 @@ class TestVm: FunSpec( { test("vm execution: modify memory") { val program = IRProgram("test", IRSymbolTable(), getTestOptions(), VMTarget()) val block = IRBlock("testmain", false, IRBlock.Options(), Position.DUMMY) - val startSub = IRSubroutine("testmain.testsub", emptyList(), null, Position.DUMMY) + val startSub = IRSubroutine("testmain.testsub", emptyList(), emptyList(), Position.DUMMY) val code = IRCodeChunk(startSub.label, null) code += IRInstruction(Opcode.NOP) code += IRInstruction(Opcode.LOAD, IRDataType.WORD, reg1=1, immediate=12345)