code size optimization: subroutine calls with 1 int arg will pass it via register instead of separate param assignment at every call site

This commit is contained in:
Irmen de Jong 2021-12-16 00:56:51 +01:00
parent 08f87c321f
commit 629117e594
5 changed files with 62 additions and 18 deletions

View File

@ -16,6 +16,7 @@ import prog8.compiler.target.cpu6502.codegen.assignment.AsmAssignTarget
import prog8.compiler.target.cpu6502.codegen.assignment.AsmAssignment
import prog8.compiler.target.cpu6502.codegen.assignment.AssignmentAsmGen
import prog8.compiler.target.cpu6502.codegen.assignment.SourceStorageKind
import prog8.compiler.target.cpu6502.codegen.assignment.TargetStorageKind
import prog8.compilerinterface.*
import prog8.parser.SourceCode
import java.nio.file.Path
@ -1015,6 +1016,16 @@ 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(!onlyVariables) {
out("; statements")
sub.statements.forEach { translate(it) }

View File

@ -67,6 +67,8 @@ 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 translateFunctionCall(call: IFunctionCall, isExpression: Boolean) {
// Output only the code to set up the parameters and perform the actual call
// NOTE: does NOT output the code to deal with the result values!
@ -76,8 +78,10 @@ internal class FunctionCallAsmGen(private val program: Program, private val asmg
val sub = call.target.targetSubroutine(program) ?: throw AssemblyError("undefined subroutine ${call.target}")
val subAsmName = asmgen.asmSymbolName(call.target)
if(!isExpression && !sub.isAsmSubroutine)
throw AssemblyError("functioncall statements to non-asmsub should have been replaced by GoSub $call")
if(!isExpression && !sub.isAsmSubroutine) {
if(!singleArgViaRegisters(sub))
throw AssemblyError("functioncall statements to non-asmsub should have been replaced by GoSub $call")
}
if(sub.isAsmSubroutine) {
argumentsViaRegisters(sub, call)
@ -98,7 +102,12 @@ internal class FunctionCallAsmGen(private val program: Program, private val asmg
if(sub.inline)
throw AssemblyError("can only reliably inline asmsub routines at this time")
argumentsViaVariables(sub, call)
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)
} else {
argumentsViaVariables(sub, call)
}
asmgen.out(" jsr $subAsmName")
}
@ -262,14 +271,14 @@ internal class FunctionCallAsmGen(private val program: Program, private val asmg
asmgen.assignExpressionToVariable(value, varName, parameter.value.type, sub)
}
private fun argumentViaRegister(sub: Subroutine, parameter: IndexedValue<SubroutineParameter>, value: Expression) {
private fun argumentViaRegister(sub: Subroutine, parameter: IndexedValue<SubroutineParameter>, value: Expression, registerOverride: RegisterOrPair? = null) {
// pass argument via a register parameter
val valueIDt = value.inferType(program)
val valueDt = valueIDt.getOrElse { throw AssemblyError("unknown dt") }
if(!isArgumentTypeCompatible(valueDt, parameter.value.type))
throw AssemblyError("argument type incompatible")
val paramRegister = sub.asmParameterRegisters[parameter.index]
val paramRegister = if(registerOverride==null) sub.asmParameterRegisters[parameter.index] else RegisterOrStatusflag(registerOverride, null)
val statusflag = paramRegister.statusflag
val register = paramRegister.registerOrPair
val requiredDt = parameter.value.type

View File

@ -392,11 +392,15 @@ internal class StatementReorderer(val program: Program,
return listOf(IAstModification.ReplaceNode(call, GoSub(null, call.target, null, call.position), parent))
}
// if(function.parameters.size==1) {
// // 1 param
// val dt = function.parameters[0].type
// if(dt in IntegerDatatypes)
// }
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
return noModifications
}
}
val assignParams =
function.parameters.zip(call.args).map {

View File

@ -324,9 +324,9 @@ class TestSubroutines: FunSpec({
val text = """
main {
sub start() {
func(1)
func(1, 2, 3)
sub func(ubyte a) {
sub func(ubyte a, ubyte b, ubyte c) {
a++
}
}

View File

@ -1,22 +1,42 @@
%import textio
%import floats
%zeropage basicsafe
main {
sub start() {
singleparamb(10)
singleparamw(2000)
singleparamb(123)
singleparamw(-9999)
doubleparamb(123,-99)
doubleparamw(8888,-9999)
singleparamf(1.23456)
}
sub singleparamb(ubyte bb) {
bb++
txt.print_ub(bb)
txt.nl()
}
sub doubleparamb(ubyte bb, byte bs) {
txt.print_ub(bb)
txt.spc()
txt.print_b(bs)
txt.nl()
}
sub singleparamw(word ww) {
ww++
txt.print_w(ww)
txt.nl()
}
sub doubleparamw(uword uw, word ww) {
txt.print_uw(uw)
txt.spc()
txt.print_w(ww)
txt.nl()
}
sub singleparamf(float ff) {
ff++
floats.print_f(ff)
txt.nl()
}
}