From 9da70bdf050e200677ed77101074ca620770f08a Mon Sep 17 00:00:00 2001 From: Irmen de Jong Date: Sat, 16 Nov 2024 00:40:28 +0100 Subject: [PATCH] simplify ReturnConvention a little --- .../src/prog8/code/core/BuiltinFunctions.kt | 38 +++++++++++-------- .../codegen/cpu6502/FunctionCallAsmGen.kt | 38 ++++++++++++------- .../codegen/cpu6502/ProgramAndVarsGen.kt | 33 +++++++++------- compiler/test/TestBuiltinFunctions.kt | 14 ------- 4 files changed, 66 insertions(+), 57 deletions(-) diff --git a/codeCore/src/prog8/code/core/BuiltinFunctions.kt b/codeCore/src/prog8/code/core/BuiltinFunctions.kt index 6304fdfce..11f8999e7 100644 --- a/codeCore/src/prog8/code/core/BuiltinFunctions.kt +++ b/codeCore/src/prog8/code/core/BuiltinFunctions.kt @@ -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, val returns: ReturnConvention) { override fun toString(): String { @@ -13,8 +13,8 @@ class CallConvention(val params: List, 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 -> "" } return "CallConvention[" + paramConvs.joinToString() + " ; returns: $returnConv]" @@ -29,19 +29,19 @@ class FSignature(val pure: Boolean, // does it have side effects? fun callConvention(actualParamTypes: List): 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) } diff --git a/codeGenCpu6502/src/prog8/codegen/cpu6502/FunctionCallAsmGen.kt b/codeGenCpu6502/src/prog8/codegen/cpu6502/FunctionCallAsmGen.kt index a23bff4f6..4f9d20192 100644 --- a/codeGenCpu6502/src/prog8/codegen/cpu6502/FunctionCallAsmGen.kt +++ b/codeGenCpu6502/src/prog8/codegen/cpu6502/FunctionCallAsmGen.kt @@ -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,17 +125,25 @@ internal class FunctionCallAsmGen(private val program: PtProgram, private val as } else if(sub is PtSub) { if(optimizeIntArgsViaRegisters(sub)) { - if(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 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])) - asmgen.out(" pha") - argumentViaRegister(sub, IndexedValue(1, sub.parameters[1]), call.args[1], RegisterOrPair.Y) - if(asmgen.needAsaveForExpr(call.args[1])) - asmgen.out(" pla") + 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) + } + 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])) + asmgen.out(" pha") + 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 diff --git a/codeGenCpu6502/src/prog8/codegen/cpu6502/ProgramAndVarsGen.kt b/codeGenCpu6502/src/prog8/codegen/cpu6502/ProgramAndVarsGen.kt index f6a803e98..dc726d83a 100644 --- a/codeGenCpu6502/src/prog8/codegen/cpu6502/ProgramAndVarsGen.kt +++ b/codeGenCpu6502/src/prog8/codegen/cpu6502/ProgramAndVarsGen.kt @@ -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) { - 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 - 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) - asmgen.assignRegister(RegisterOrPair.A, target1) - asmgen.assignRegister(RegisterOrPair.Y, target2) + 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) + } + 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") } } diff --git a/compiler/test/TestBuiltinFunctions.kt b/compiler/test/TestBuiltinFunctions.kt index 699607628..e55eb9fc8 100644 --- a/compiler/test/TestBuiltinFunctions.kt +++ b/compiler/test/TestBuiltinFunctions.kt @@ -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") {