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

This commit is contained in:
Irmen de Jong 2021-12-16 01:34:36 +01:00
parent 629117e594
commit 3cf9b9d9a5
5 changed files with 52 additions and 26 deletions

View File

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

View File

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

View File

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

View File

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

View File

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