simplify ReturnConvention a little

This commit is contained in:
Irmen de Jong 2024-11-16 00:40:28 +01:00
parent d640cfbe13
commit 9da70bdf05
4 changed files with 66 additions and 57 deletions

View File

@ -1,6 +1,6 @@
package prog8.code.core
class ReturnConvention(val dt: DataType?, val reg: RegisterOrPair?, val floatFac1: Boolean)
class ReturnConvention(val dt: DataType?, val reg: RegisterOrPair?)
class ParamConvention(val dt: DataType, val reg: RegisterOrPair?, val variable: Boolean)
class CallConvention(val params: List<ParamConvention>, val returns: ReturnConvention) {
override fun toString(): String {
@ -13,8 +13,8 @@ class CallConvention(val params: List<ParamConvention>, val returns: ReturnConve
}
val returnConv =
when {
returns.reg!=null -> returns.reg.toString()
returns.floatFac1 -> "floatFAC1"
returns.reg == RegisterOrPair.FAC1 -> "floatFAC1"
returns.reg != null -> returns.reg.toString()
else -> "<no returnvalue>"
}
return "CallConvention[" + paramConvs.joinToString() + " ; returns: $returnConv]"
@ -29,19 +29,19 @@ class FSignature(val pure: Boolean, // does it have side effects?
fun callConvention(actualParamTypes: List<DataType>): CallConvention {
val returns: ReturnConvention = when (returnType) {
DataType.UBYTE, DataType.BYTE -> ReturnConvention(returnType, RegisterOrPair.A, false)
DataType.UWORD, DataType.WORD -> ReturnConvention(returnType, RegisterOrPair.AY, false)
DataType.FLOAT -> ReturnConvention(returnType, null, true)
in PassByReferenceDatatypes -> ReturnConvention(returnType!!, RegisterOrPair.AY, false)
null -> ReturnConvention(null, null, false)
DataType.UBYTE, DataType.BYTE -> ReturnConvention(returnType, RegisterOrPair.A)
DataType.UWORD, DataType.WORD -> ReturnConvention(returnType, RegisterOrPair.AY)
DataType.FLOAT -> ReturnConvention(returnType, RegisterOrPair.FAC1)
in PassByReferenceDatatypes -> ReturnConvention(returnType!!, RegisterOrPair.AY)
null -> ReturnConvention(null, null)
else -> {
// return type depends on arg type
when (val paramType = actualParamTypes.first()) {
DataType.UBYTE, DataType.BYTE -> ReturnConvention(paramType, RegisterOrPair.A, false)
DataType.UWORD, DataType.WORD -> ReturnConvention(paramType, RegisterOrPair.AY, false)
DataType.FLOAT -> ReturnConvention(paramType, null, true)
in PassByReferenceDatatypes -> ReturnConvention(paramType, RegisterOrPair.AY, false)
else -> ReturnConvention(paramType, null, false)
DataType.UBYTE, DataType.BYTE -> ReturnConvention(paramType, RegisterOrPair.A)
DataType.UWORD, DataType.WORD -> ReturnConvention(paramType, RegisterOrPair.AY)
DataType.FLOAT -> ReturnConvention(paramType, RegisterOrPair.FAC1)
in PassByReferenceDatatypes -> ReturnConvention(paramType, RegisterOrPair.AY)
else -> ReturnConvention(paramType, null)
}
}
}
@ -52,16 +52,24 @@ class FSignature(val pure: Boolean, // does it have side effects?
// One parameter goes via register/registerpair.
// this avoids repeated code for every caller to store the value in the subroutine's argument variable.
// (that store is still done, but only coded once at the start at the subroutine itself rather than at every call site).
// TODO can we start using the X register as well for a third byte or word+byte combo
val paramConv = when(val paramType = actualParamTypes[0]) {
DataType.UBYTE, DataType.BYTE -> ParamConvention(paramType, RegisterOrPair.A, false)
DataType.UWORD, DataType.WORD -> ParamConvention(paramType, RegisterOrPair.AY, false)
DataType.FLOAT -> ParamConvention(paramType, RegisterOrPair.AY, false) // TODO is this correct? shouldn't it be FAC1?
DataType.FLOAT -> ParamConvention(paramType, RegisterOrPair.AY, false) // NOTE: for builtin functions, floating point arguments are passed by reference (so you get a pointer in AY)
in PassByReferenceDatatypes -> ParamConvention(paramType, RegisterOrPair.AY, false)
else -> ParamConvention(paramType, null, false)
}
CallConvention(listOf(paramConv), returns)
}
actualParamTypes.size==2 && (actualParamTypes[0] in ByteDatatypes && actualParamTypes[1] in WordDatatypes) -> {
TODO("opportunity to pass word+byte arguments in A,Y and X registers but not implemented yet")
}
actualParamTypes.size==2 && (actualParamTypes[0] in WordDatatypes && actualParamTypes[1] in ByteDatatypes) -> {
TODO("opportunity to pass word+byte arguments in A,Y and X registers but not implemented yet")
}
actualParamTypes.size==3 && actualParamTypes.all { it in ByteDatatypes } -> {
TODO("opportunity to pass 3 byte arguments in A,Y and X registers but not implemented yet")
}
else -> {
// multiple parameters go via variables
val paramConvs = actualParamTypes.map { ParamConvention(it, null, true) }

View File

@ -15,10 +15,12 @@ internal class FunctionCallAsmGen(private val program: PtProgram, private val as
// just ignore any result values from the function call.
}
// TODO tweak subroutine call convention to also make use of X register to pass a third byte?
internal fun optimizeIntArgsViaRegisters(sub: PtSub) =
(sub.parameters.size==1 && sub.parameters[0].type in IntegerDatatypesWithBoolean)
|| (sub.parameters.size==2 && sub.parameters[0].type in ByteDatatypesWithBoolean && sub.parameters[1].type in ByteDatatypesWithBoolean)
when(sub.parameters.size) {
1 -> sub.parameters[0].type in IntegerDatatypesWithBoolean
2 -> sub.parameters[0].type in ByteDatatypesWithBoolean && sub.parameters[1].type in ByteDatatypesWithBoolean
else -> false
}
internal fun translateFunctionCall(call: PtFunctionCall) {
// Output only the code to set up the parameters and perform the actual call
@ -123,10 +125,13 @@ internal class FunctionCallAsmGen(private val program: PtProgram, private val as
}
else if(sub is PtSub) {
if(optimizeIntArgsViaRegisters(sub)) {
if(sub.parameters.size==1) {
when(sub.parameters.size) {
1 -> {
val register = if (sub.parameters[0].type in ByteDatatypesWithBoolean) RegisterOrPair.A else RegisterOrPair.AY
argumentViaRegister(sub, IndexedValue(0, sub.parameters[0]), call.args[0], register)
} else {
}
2 -> {
if(sub.parameters[0].type in ByteDatatypesWithBoolean && sub.parameters[1].type in ByteDatatypesWithBoolean) {
// 2 byte params, second in Y, first in A
argumentViaRegister(sub, IndexedValue(0, sub.parameters[0]), call.args[0], RegisterOrPair.A)
if(asmgen.needAsaveForExpr(call.args[1]))
@ -134,6 +139,11 @@ internal class FunctionCallAsmGen(private val program: PtProgram, private val as
argumentViaRegister(sub, IndexedValue(1, sub.parameters[1]), call.args[1], RegisterOrPair.Y)
if(asmgen.needAsaveForExpr(call.args[1]))
asmgen.out(" pla")
} else {
throw AssemblyError("cannot use registers for word+byte")
}
}
else -> throw AssemblyError("cannot use registers for >2 arguments")
}
} else {
// arguments via variables

View File

@ -423,20 +423,25 @@ internal class ProgramAndVarsGen(
if(functioncallAsmGen.optimizeIntArgsViaRegisters(sub)) {
asmgen.out("; simple int arg(s) passed via register(s)")
if(sub.parameters.size==1) {
when(sub.parameters.size) {
1 -> {
val dt = sub.parameters[0].type
val target = AsmAssignTarget(TargetStorageKind.VARIABLE, asmgen, dt, sub, sub.parameters[0].position, variableAsmName = sub.parameters[0].name)
if(dt in ByteDatatypesWithBoolean)
asmgen.assignRegister(RegisterOrPair.A, target)
else
asmgen.assignRegister(RegisterOrPair.AY, target)
} else {
require(sub.parameters.size==2)
// 2 simple byte args, first in A, second in Y
}
2 -> {
val target1 = AsmAssignTarget(TargetStorageKind.VARIABLE, asmgen, sub.parameters[0].type, sub, sub.parameters[0].position, variableAsmName = sub.parameters[0].name)
val target2 = AsmAssignTarget(TargetStorageKind.VARIABLE, asmgen, sub.parameters[1].type, sub, sub.parameters[1].position, variableAsmName = sub.parameters[1].name)
if(sub.parameters[0].type in ByteDatatypesWithBoolean && sub.parameters[1].type in ByteDatatypesWithBoolean) {
// 2 byte args, first in A, second in Y
asmgen.assignRegister(RegisterOrPair.A, target1)
asmgen.assignRegister(RegisterOrPair.Y, target2)
} else throw AssemblyError("cannot use registers for word+byte")
}
else -> throw AssemblyError("cannot use registers for >2 arguments")
}
}

View File

@ -29,7 +29,6 @@ class TestBuiltinFunctions: FunSpec({
conv.params[0].reg shouldBe RegisterOrPair.A
conv.params[0].variable shouldBe false
conv.returns.dt shouldBe DataType.BYTE
conv.returns.floatFac1 shouldBe false
conv.returns.reg shouldBe RegisterOrPair.A
}
@ -42,7 +41,6 @@ class TestBuiltinFunctions: FunSpec({
val conv = func.callConvention(listOf(DataType.UWORD, DataType.UWORD))
conv.params.size shouldBe 2
conv.returns.dt shouldBe null
conv.returns.floatFac1 shouldBe false
conv.returns.reg shouldBe null
}
@ -55,18 +53,6 @@ class TestBuiltinFunctions: FunSpec({
func.parameters[1].possibleDatatypes shouldBe arrayOf(DataType.UBYTE)
func.pure shouldBe false
func.returnType shouldBe null
val conv = func.callConvention(listOf(DataType.UWORD, DataType.UBYTE))
conv.params.size shouldBe 2
conv.params[0].dt shouldBe DataType.UWORD
conv.params[0].reg shouldBe null
conv.params[0].variable shouldBe true
conv.params[1].dt shouldBe DataType.UBYTE
conv.params[1].reg shouldBe null
conv.params[1].variable shouldBe true
conv.returns.dt shouldBe null
conv.returns.floatFac1 shouldBe false
conv.returns.reg shouldBe null
}
test("certain builtin functions should be compile time evaluated") {