From 3cf9b9d9a5f1ac4c1c4ac59272f8fb5ec207cf4d Mon Sep 17 00:00:00 2001 From: Irmen de Jong Date: Thu, 16 Dec 2021 01:34:36 +0100 Subject: [PATCH] code size optimization: subroutine calls with 2 byte arg will pass it via A/Y registers instead of separate param assignments at every call site --- .../compiler/target/cpu6502/codegen/AsmGen.kt | 25 +++++++++++++------ .../cpu6502/codegen/FunctionCallAsmGen.kt | 22 ++++++++++++---- .../astprocessing/StatementReorderer.kt | 14 +++++++---- docs/source/todo.rst | 6 ----- examples/test.p8 | 11 ++++++-- 5 files changed, 52 insertions(+), 26 deletions(-) diff --git a/codeGeneration/src/prog8/compiler/target/cpu6502/codegen/AsmGen.kt b/codeGeneration/src/prog8/compiler/target/cpu6502/codegen/AsmGen.kt index 6295e3909..eb7fbde2d 100644 --- a/codeGeneration/src/prog8/compiler/target/cpu6502/codegen/AsmGen.kt +++ b/codeGeneration/src/prog8/compiler/target/cpu6502/codegen/AsmGen.kt @@ -1016,14 +1016,23 @@ class AsmGen(private val program: Program, clc""") } - if(functioncallAsmGen.singleArgViaRegisters(sub)) { - out("; single arg is passed via register(s)") - val dt = sub.parameters[0].type - val target = AsmAssignTarget(TargetStorageKind.VARIABLE, program, this, dt, sub, variableAsmName = sub.parameters[0].name) - if(dt in ByteDatatypes) - assignRegister(RegisterOrPair.A, target) - else - assignRegister(RegisterOrPair.AY, target) + if(functioncallAsmGen.optimizeIntArgsViaRegisters(sub)) { + 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, program, this, dt, sub, variableAsmName = sub.parameters[0].name) + if(dt in ByteDatatypes) + assignRegister(RegisterOrPair.A, target) + else + 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, program, this, sub.parameters[0].type, sub, variableAsmName = sub.parameters[0].name) + val target2 = AsmAssignTarget(TargetStorageKind.VARIABLE, program, this, sub.parameters[1].type, sub, variableAsmName = sub.parameters[1].name) + assignRegister(RegisterOrPair.A, target1) + assignRegister(RegisterOrPair.Y, target2) + } } if(!onlyVariables) { diff --git a/codeGeneration/src/prog8/compiler/target/cpu6502/codegen/FunctionCallAsmGen.kt b/codeGeneration/src/prog8/compiler/target/cpu6502/codegen/FunctionCallAsmGen.kt index e8c8ff55d..2396b5bef 100644 --- a/codeGeneration/src/prog8/compiler/target/cpu6502/codegen/FunctionCallAsmGen.kt +++ b/codeGeneration/src/prog8/compiler/target/cpu6502/codegen/FunctionCallAsmGen.kt @@ -67,7 +67,9 @@ internal class FunctionCallAsmGen(private val program: Program, private val asmg } } - internal fun singleArgViaRegisters(sub: Subroutine) = sub.parameters.size==1 && sub.parameters[0].type in IntegerDatatypes + internal fun optimizeIntArgsViaRegisters(sub: Subroutine) = + (sub.parameters.size==1 && sub.parameters[0].type in IntegerDatatypes) + || (sub.parameters.size==2 && sub.parameters[0].type in ByteDatatypes && sub.parameters[1].type in ByteDatatypes) internal fun translateFunctionCall(call: IFunctionCall, isExpression: Boolean) { // Output only the code to set up the parameters and perform the actual call @@ -79,7 +81,7 @@ internal class FunctionCallAsmGen(private val program: Program, private val asmg val subAsmName = asmgen.asmSymbolName(call.target) if(!isExpression && !sub.isAsmSubroutine) { - if(!singleArgViaRegisters(sub)) + if(!optimizeIntArgsViaRegisters(sub)) throw AssemblyError("functioncall statements to non-asmsub should have been replaced by GoSub $call") } @@ -102,9 +104,19 @@ internal class FunctionCallAsmGen(private val program: Program, private val asmg if(sub.inline) throw AssemblyError("can only reliably inline asmsub routines at this time") - if(singleArgViaRegisters(sub)) { - val register = if(sub.parameters[0].type in ByteDatatypes) RegisterOrPair.A else RegisterOrPair.AY - argumentViaRegister(sub, IndexedValue(0, sub.parameters[0]), call.args[0], register) + if(optimizeIntArgsViaRegisters(sub)) { + if(sub.parameters.size==1) { + val register = if (sub.parameters[0].type in ByteDatatypes) 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(!call.args[1].isSimple) + asmgen.out(" pha") + argumentViaRegister(sub, IndexedValue(1, sub.parameters[1]), call.args[1], RegisterOrPair.Y) + if(!call.args[1].isSimple) + asmgen.out(" pla") + } } else { argumentsViaVariables(sub, call) } diff --git a/compiler/src/prog8/compiler/astprocessing/StatementReorderer.kt b/compiler/src/prog8/compiler/astprocessing/StatementReorderer.kt index 289c32c4e..2d5add6c6 100644 --- a/compiler/src/prog8/compiler/astprocessing/StatementReorderer.kt +++ b/compiler/src/prog8/compiler/astprocessing/StatementReorderer.kt @@ -8,6 +8,7 @@ import prog8.ast.walk.AstWalker import prog8.ast.walk.IAstModification import prog8.compilerinterface.BuiltinFunctions import prog8.compilerinterface.CompilationOptions +import prog8.compilerinterface.ICompilationTarget import prog8.compilerinterface.IErrorReporter @@ -393,11 +394,14 @@ internal class StatementReorderer(val program: Program, } if(function.parameters.size==1) { - // 1 param - val dt = function.parameters[0].type - if(dt in IntegerDatatypes) { - // optimization: 1 integer param is passed via registers directly, not by assignment to param variable - // TODO also do this for 2x byte param , can be put in A and Y + if(function.parameters[0].type in IntegerDatatypes) { + // optimization: 1 integer param is passed via register(s) directly, not by assignment to param variable + return noModifications + } + } + else if(function.parameters.size==2) { + if(function.parameters[0].type in ByteDatatypes && function.parameters[1].type in ByteDatatypes) { + // optimization: 2 simple byte param is passed via 2 registers directly, not by assignment to param variables return noModifications } } diff --git a/docs/source/todo.rst b/docs/source/todo.rst index bce03661b..72167e591 100644 --- a/docs/source/todo.rst +++ b/docs/source/todo.rst @@ -3,12 +3,6 @@ TODO For next compiler release (7.6) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -optimization in call convention: -non-asm subroutines with just a single byte or word parameter: - pass the parameter via A or A/Y registers. - add code to set the parameter variable in the start of the subroutine itself, - rather than requiring the caller to set it there. This is not faster but saves a lot of bytes of code. - ... diff --git a/examples/test.p8 b/examples/test.p8 index 6a2f173d9..b550d36d1 100644 --- a/examples/test.p8 +++ b/examples/test.p8 @@ -4,10 +4,17 @@ main { sub start() { + ubyte xx = 0 + + singleparamb(123) singleparamb(123) singleparamw(-9999) - doubleparamb(123,-99) - doubleparamw(8888,-9999) + singleparamw(-9999) + doubleparamb(xx+111,-99) + doubleparamb(xx+111,-99) + doubleparamw(xx+8888,-9999) + doubleparamw(xx+8888,-9999) + singleparamf(1.23456) singleparamf(1.23456) }