optimized arg passing if all args are registers

This commit is contained in:
Irmen de Jong 2020-07-04 18:43:29 +02:00
parent 71e678b382
commit cdcb652033
3 changed files with 56 additions and 114 deletions

View File

@ -178,8 +178,8 @@ internal class ExpressionsAsmGen(private val program: Program, private val asmge
private fun translateExpression(expr: RegisterExpr) {
when(expr.register) {
Register.A -> asmgen.out(" sta $ESTACK_LO_HEX,x | dex")
Register.X -> asmgen.out(" txa | sta $ESTACK_LO_HEX,x | dex")
Register.Y -> asmgen.out(" tya | sta $ESTACK_LO_HEX,x | dex")
Register.X -> asmgen.out(" pha | txa | sta $ESTACK_LO_HEX,x | dex | pla")
Register.Y -> asmgen.out(" pha | tya | sta $ESTACK_LO_HEX,x | dex | pla")
}
}

View File

@ -5,7 +5,6 @@ import prog8.ast.Program
import prog8.ast.base.*
import prog8.ast.expressions.*
import prog8.ast.statements.AssignTarget
import prog8.ast.statements.RegisterOrStatusflag
import prog8.ast.statements.Subroutine
import prog8.ast.statements.SubroutineParameter
import prog8.compiler.AssemblyError
@ -39,32 +38,29 @@ internal class FunctionCallAsmGen(private val program: Program, private val asmg
} else {
// multiple register arguments, risk of register clobbering.
// evaluate arguments onto the stack, and load the registers from the evaluated values on the stack.
// TODO optimize this for the cases where all args aren't expressions (no risk of clobbering in that case)
for(arg in stmt.args.reversed())
asmgen.translateExpression(arg)
for(regparam in sub.asmParameterRegisters) {
when(regparam.registerOrPair) {
RegisterOrPair.A -> asmgen.out(" inx | lda $ESTACK_LO_HEX,x")
RegisterOrPair.X -> throw AssemblyError("can't pop into X register - use a variable instead")
RegisterOrPair.Y -> asmgen.out(" inx | ldy $ESTACK_LO_HEX,x")
RegisterOrPair.AX -> throw AssemblyError("can't pop into X register - use a variable instead")
RegisterOrPair.AY -> asmgen.out(" inx | lda $ESTACK_LO_HEX,x | ldy $ESTACK_HI_HEX,x")
RegisterOrPair.XY -> throw AssemblyError("can't pop into X register - use a variable instead")
null -> {}
when {
stmt.args.all {it is AddressOf ||
it is NumericLiteralValue ||
it is StructLiteralValue ||
it is StringLiteralValue ||
it is ArrayLiteralValue ||
it is IdentifierReference} -> {
// no risk of clobbering for these simple argument types. Optimize the register loading.
for(arg in sub.parameters.withIndex().zip(stmt.args)) {
argumentViaRegister(sub, arg.first, arg.second)
}
}
when(regparam.statusflag) {
Statusflag.Pc -> asmgen.out("""
inx
pha
lda $ESTACK_LO_HEX,x
beq +
sec
bcs ++
+ clc
+ pla
""")
null -> {}
else -> throw AssemblyError("can only use Carry as status flag parameter")
stmt.args.all {it is RegisterExpr} -> {
val argRegisters = stmt.args.map {(it as RegisterExpr).register.toString()}
val paramRegisters = sub.asmParameterRegisters.map { it.registerOrPair?.toString() }
if(argRegisters != paramRegisters) {
// all args are registers but differ from the function params. Can't pass directly, work via stack.
argsViaStackEvaluation(stmt, sub)
}
}
else -> {
// Risk of clobbering due to complex expression args. Work via the stack.
argsViaStackEvaluation(stmt, sub)
}
}
}
@ -76,6 +72,38 @@ internal class FunctionCallAsmGen(private val program: Program, private val asmg
asmgen.out(" ldx c64.SCRATCH_ZPREGX") // restore X again
}
private fun argsViaStackEvaluation(stmt: IFunctionCall, sub: Subroutine) {
for (arg in stmt.args.reversed())
asmgen.translateExpression(arg)
for (regparam in sub.asmParameterRegisters) {
when (regparam.registerOrPair) {
RegisterOrPair.A -> asmgen.out(" inx | lda $ESTACK_LO_HEX,x")
RegisterOrPair.X -> throw AssemblyError("can't pop into X register - use a variable instead")
RegisterOrPair.Y -> asmgen.out(" inx | ldy $ESTACK_LO_HEX,x")
RegisterOrPair.AX -> throw AssemblyError("can't pop into X register - use a variable instead")
RegisterOrPair.AY -> asmgen.out(" inx | lda $ESTACK_LO_HEX,x | ldy $ESTACK_HI_HEX,x")
RegisterOrPair.XY -> throw AssemblyError("can't pop into X register - use a variable instead")
null -> {
}
}
when (regparam.statusflag) {
Statusflag.Pc -> asmgen.out("""
inx
pha
lda $ESTACK_LO_HEX,x
beq +
sec
bcs ++
+ clc
+ pla
""")
null -> {
}
else -> throw AssemblyError("can only use Carry as status flag parameter")
}
}
}
private fun argumentViaVariable(sub: Subroutine, parameter: IndexedValue<SubroutineParameter>, value: Expression) {
// pass parameter via a regular variable (not via registers)
val valueIDt = value.inferType(program)

View File

@ -1,86 +0,0 @@
%import c64lib
%import c64utils
%import c64flt
%zeropage basicsafe
%option enable_floats
; TODO: optimize register arg value passing when there is no risk of register clobbering
main {
sub start() {
function(20, calculate())
asmfunction(20, calculate())
asmfunction2(1, 2) ; TODO optimize
ubyte arg = 3
ubyte arg2 = 4
asmfunction3(arg, arg2) ; TODO optimize
Y=5
A=6
asmfunction4(Y,A) ; TODO optimize
A=7
Y=8
asmfunction5(A,Y) ; TODO cannot optimize, fix result
c64.CHROUT('\n')
if @($0400)==@($0402) and @($0401) == @($0403) {
c64scr.print("ok: results are same\n")
} else {
c64scr.print("error: result differ; arg got clobbered\n")
}
}
sub function(ubyte a1, ubyte a2) {
; non-asm function passes via stack, this is ok
@($0400) = a1
@($0401) = a2
}
asmsub asmfunction(ubyte a1 @ Y, ubyte a2 @ A) {
; asm-function passes via registers, risk of clobbering
%asm {{
sty $0402
sta $0403
}}
}
asmsub asmfunction2(ubyte a1 @ Y, ubyte a2 @ A) {
; asm-function passes via registers, risk of clobbering
%asm {{
sty $0404
sta $0405
}}
}
asmsub asmfunction3(ubyte a1 @ Y, ubyte a2 @ A) {
; asm-function passes via registers, risk of clobbering
%asm {{
sty $0406
sta $0407
}}
}
asmsub asmfunction4(ubyte a1 @ Y, ubyte a2 @ A) {
; asm-function passes via registers, risk of clobbering
%asm {{
sty $0408
sta $0409
}}
}
asmsub asmfunction5(ubyte a1 @ Y, ubyte a2 @ A) {
; asm-function passes via registers, risk of clobbering
%asm {{
sty $040a
sta $040b
}}
}
sub calculate() -> ubyte {
Y = 99
return Y
}
}