fixed possible register subroutine arg clobbering

This commit is contained in:
Irmen de Jong 2020-07-04 17:04:49 +02:00
parent 3050156325
commit 71e678b382
5 changed files with 248 additions and 191 deletions

View File

@ -5,6 +5,7 @@ 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
@ -25,8 +26,48 @@ internal class FunctionCallAsmGen(private val program: Program, private val asmg
val subName = asmgen.asmIdentifierName(stmt.target)
if(stmt.args.isNotEmpty()) {
if(sub.asmParameterRegisters.isEmpty()) {
// via variables
for(arg in sub.parameters.withIndex().zip(stmt.args)) {
translateFuncArguments(arg.first, arg.second, sub)
argumentViaVariable(sub, arg.first, arg.second)
}
} else {
// via registers
if(sub.parameters.size==1) {
// just a single parameter, no risk of clobbering registers
argumentViaRegister(sub, sub.parameters.withIndex().single(), stmt.args[0])
} 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(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")
}
}
}
}
}
asmgen.out(" jsr $subName")
@ -35,15 +76,15 @@ internal class FunctionCallAsmGen(private val program: Program, private val asmg
asmgen.out(" ldx c64.SCRATCH_ZPREGX") // restore X again
}
private fun translateFuncArguments(parameter: IndexedValue<SubroutineParameter>, value: Expression, sub: Subroutine) {
val sourceIDt = value.inferType(program)
if(!sourceIDt.isKnown)
throw AssemblyError("arg type unknown")
val sourceDt = sourceIDt.typeOrElse(DataType.STRUCT)
if(!argumentTypeCompatible(sourceDt, parameter.value.type))
throw AssemblyError("argument type incompatible")
if(sub.asmParameterRegisters.isEmpty()) {
private fun argumentViaVariable(sub: Subroutine, parameter: IndexedValue<SubroutineParameter>, value: Expression) {
// pass parameter via a regular variable (not via registers)
val valueIDt = value.inferType(program)
if(!valueIDt.isKnown)
throw AssemblyError("arg type unknown")
val valueDt = valueIDt.typeOrElse(DataType.STRUCT)
if(!argumentTypeCompatible(valueDt, parameter.value.type))
throw AssemblyError("argument type incompatible")
val paramVar = parameter.value
val scopedParamVar = (sub.scopedname+"."+paramVar.name).split(".")
val target = AssignTarget(null, IdentifierReference(scopedParamVar, sub.position), null, null, sub.position)
@ -93,8 +134,17 @@ internal class FunctionCallAsmGen(private val program: Program, private val asmg
asmgen.assignFromEvalResult(target)
}
}
} else {
// pass parameter via a register parameter
}
private fun argumentViaRegister(sub: Subroutine, parameter: IndexedValue<SubroutineParameter>, value: Expression) {
// pass argument via a register parameter
val valueIDt = value.inferType(program)
if(!valueIDt.isKnown)
throw AssemblyError("arg type unknown")
val valueDt = valueIDt.typeOrElse(DataType.STRUCT)
if(!argumentTypeCompatible(valueDt, parameter.value.type))
throw AssemblyError("argument type incompatible")
val paramRegister = sub.asmParameterRegisters[parameter.index]
val statusflag = paramRegister.statusflag
val register = paramRegister.registerOrPair
@ -143,12 +193,13 @@ internal class FunctionCallAsmGen(private val program: Program, private val asmg
asmgen.translateExpression(value)
asmgen.out("""
inx
pha
lda $ESTACK_LO_HEX,x
beq +
sec
bcs ++
+ clc
+
+ pla
""")
}
}
@ -203,7 +254,7 @@ internal class FunctionCallAsmGen(private val program: Program, private val asmg
}
is IdentifierReference -> {
val sourceName = asmgen.asmIdentifierName(value)
if(sourceDt in PassByReferenceDatatypes) {
if(valueDt in PassByReferenceDatatypes) {
when (register) {
RegisterOrPair.AX -> asmgen.out(" lda #<$sourceName | ldx #>$sourceName")
RegisterOrPair.AY -> asmgen.out(" lda #<$sourceName | ldy #>$sourceName")
@ -230,7 +281,6 @@ internal class FunctionCallAsmGen(private val program: Program, private val asmg
}
}
}
}
private fun argumentTypeCompatible(argType: DataType, paramType: DataType): Boolean {
if(argType isAssignableTo paramType)

View File

@ -2,9 +2,6 @@
TODO
====
- BUG FIX: fix register argument clobbering when calling asmsubs. (see fixme_argclobber.p8)
- finalize (most) of the still missing "new" assignment asm code generation
- aliases for imported symbols for example perhaps '%alias print = c64scr.print'
- option to load library files from a directory instead of the embedded ones (easier library development/debugging)

View File

@ -5,15 +5,23 @@
%option enable_floats
; TODO: fix register argument clobbering when calling asmsubs.
; for instance if the first arg goes into Y, and the second in A,
; but when calculating the second argument clobbers Y, the first argument gets destroyed.
; 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')
@ -38,6 +46,37 @@ main {
}}
}
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

View File

@ -8,10 +8,6 @@
; some simple sound effects
; TODO fix noCollision() at bottom when compiled without optimizations (codegen issue).
main {
const ubyte boardOffsetX = 14
@ -549,7 +545,6 @@ blocklogic {
sub noCollision(ubyte xpos, ubyte ypos) -> ubyte {
ubyte i
for i in 15 downto 0 {
; TODO FIX THIS when compiling without optimizations (codegen problem: clobbering register arguments, see fixme_argclobber):
if currentBlock[i] and c64scr.getchr(xpos + (i&3), ypos+i/4)!=32
return false
}

View File

@ -5,32 +5,8 @@
%option enable_floats
; TODO: fix register argument clobbering when calling asmsubs.
; for instance if the first arg goes into Y, and the second in A,
; but when calculating the second argument clobbers Y, the first argument gets destroyed.
main {
sub start() {
turtle.pu()
turtle.pu()
turtle.pu()
turtle.pu()
turtle.pu()
turtle.pu()
turtle.pu()
turtle.pu()
turtle.pu()
turtle.pu()
}
}
turtle {
ubyte pendown
sub pu() {
pendown = false
}
}