working on codegen for multi-value returns

This commit is contained in:
Irmen de Jong 2025-01-07 02:08:30 +01:00
parent ca9422bbe9
commit 8f6b5676d7
28 changed files with 210 additions and 196 deletions

View File

@ -259,7 +259,7 @@ class StMemorySlab(
StNode(name, StNodeType.MEMORYSLAB, astNode)
class StSub(name: String, val parameters: List<StSubroutineParameter>, val returnType: DataType?, astNode: PtNode) :
class StSub(name: String, val parameters: List<StSubroutineParameter>, val returns: List<DataType>, astNode: PtNode) :
StNode(name, StNodeType.SUBROUTINE, astNode)

View File

@ -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?

View File

@ -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
}
}

View File

@ -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 -> {

View File

@ -25,15 +25,15 @@ class PtAsmSub(
class PtSub(
name: String,
val parameters: List<PtSubroutineParameter>,
val returntype: DataType?,
val returns: List<DataType>,
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 }
}
}

View File

@ -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")
}

View File

@ -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<Pair<RegisterOrStatusflag, D
return returns
}
is PtSub -> {
// 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)
}
}

View File

@ -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 -> {

View File

@ -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")
}
}
}
}

View File

@ -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),

View File

@ -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<IRCodeChunkBase>()
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)

View File

@ -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<IRCodeChunkBase>()

View File

@ -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 }
}

View File

@ -9,7 +9,7 @@ class TestIRPeepholeOpt: FunSpec({
fun makeIRProgram(chunks: List<IRCodeChunkBase>): 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()

View File

@ -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)

View File

@ -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.size<expectedReturnValues.size) {
errors.err("too few return values for the subroutine: expected ${expectedReturnValues.size} got ${returnStmt.values.size}", returnStmt.position)
}
@ -404,11 +400,6 @@ internal class AstChecker(private val program: Program,
super.visit(subroutine)
// user-defined subroutines can only have zero or one return type
// (multiple return values are only allowed for asm subs)
if(!subroutine.isAsmSubroutine && subroutine.returntypes.size>1)
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)) {

View File

@ -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
}

View File

@ -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<PtSub
}
private fun integrateDefers(subdefers: Map<PtSub, List<PtDefer>>, program: PtProgram, st: SymbolTable) {
private fun integrateDefers(subdefers: Map<PtSub, List<PtDefer>>, program: PtProgram, st: SymbolTable, errors: IErrorReporter) {
val jumpsAndCallsToAugment = mutableListOf<PtNode>()
val returnsToAugment = mutableListOf<PtReturn>()
val subEndsToAugment = mutableListOf<PtSub>()
@ -149,15 +149,14 @@ private fun integrateDefers(subdefers: Map<PtSub, List<PtDefer>>, 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<PtSub, List<PtDefer>>, 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)

View File

@ -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<IAstModification>()
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<IAstModification>()
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<IAstModification> {

View File

@ -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)

View File

@ -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)

View File

@ -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?

View File

@ -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
}
}

View File

@ -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)

View File

@ -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")

View File

@ -427,7 +427,7 @@ sealed interface IIRBlockElement {
class IRSubroutine(
override val label: String,
val parameters: List<IRParam>,
val returnType: DataType?,
val returns: List<DataType>,
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) {

View File

@ -76,7 +76,7 @@ load.b r1,42
</INITGLOBALS>
<BLOCK NAME="main" ADDRESS="" LIBRARY="false" FORCEOUTPUT="false" NOPREFIXING="false" VERAFXMULS="false" ALIGN="NONE" POS="[examples/test.p8: line 2 col 2-5]">
<SUB NAME="main.start" RETURNTYPE="" POS="[examples/test.p8: line 4 col 6-8]">
<SUB NAME="main.start" RETURNS="" POS="[examples/test.p8: line 4 col 6-8]">
<PARAMS>
</PARAMS>
<CODE LABEL="main.start"><REGS>dummy</REGS>
@ -86,7 +86,7 @@ return
</BLOCK>
<BLOCK NAME="sys" ADDRESS="" LIBRARY="false" FORCEOUTPUT="false" ALIGN="NONE" POS="[library:/prog8lib/virtual/syslib.p8: line 3 col 2-4]">
<SUB NAME="sys.wait" RETURNTYPE="" POS="[library:/prog8lib/virtual/syslib.p8: line 15 col 6-8]">
<SUB NAME="sys.wait" RETURNS="" POS="[library:/prog8lib/virtual/syslib.p8: line 15 col 6-8]">
<PARAMS>
uword sys.wait.jiffies
</PARAMS>

View File

@ -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)