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.base.*
import prog8.ast.expressions.* import prog8.ast.expressions.*
import prog8.ast.statements.AssignTarget import prog8.ast.statements.AssignTarget
import prog8.ast.statements.RegisterOrStatusflag
import prog8.ast.statements.Subroutine import prog8.ast.statements.Subroutine
import prog8.ast.statements.SubroutineParameter import prog8.ast.statements.SubroutineParameter
import prog8.compiler.AssemblyError import prog8.compiler.AssemblyError
@ -25,8 +26,48 @@ internal class FunctionCallAsmGen(private val program: Program, private val asmg
val subName = asmgen.asmIdentifierName(stmt.target) val subName = asmgen.asmIdentifierName(stmt.target)
if(stmt.args.isNotEmpty()) { if(stmt.args.isNotEmpty()) {
for(arg in sub.parameters.withIndex().zip(stmt.args)) { if(sub.asmParameterRegisters.isEmpty()) {
translateFuncArguments(arg.first, arg.second, sub) // via variables
for(arg in sub.parameters.withIndex().zip(stmt.args)) {
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") asmgen.out(" jsr $subName")
@ -35,88 +76,97 @@ internal class FunctionCallAsmGen(private val program: Program, private val asmg
asmgen.out(" ldx c64.SCRATCH_ZPREGX") // restore X again asmgen.out(" ldx c64.SCRATCH_ZPREGX") // restore X again
} }
private fun translateFuncArguments(parameter: IndexedValue<SubroutineParameter>, value: Expression, sub: Subroutine) { private fun argumentViaVariable(sub: Subroutine, parameter: IndexedValue<SubroutineParameter>, value: Expression) {
val sourceIDt = value.inferType(program) // pass parameter via a regular variable (not via registers)
if(!sourceIDt.isKnown) val valueIDt = value.inferType(program)
if(!valueIDt.isKnown)
throw AssemblyError("arg type unknown") throw AssemblyError("arg type unknown")
val sourceDt = sourceIDt.typeOrElse(DataType.STRUCT) val valueDt = valueIDt.typeOrElse(DataType.STRUCT)
if(!argumentTypeCompatible(sourceDt, parameter.value.type)) if(!argumentTypeCompatible(valueDt, parameter.value.type))
throw AssemblyError("argument type incompatible") throw AssemblyError("argument type incompatible")
if(sub.asmParameterRegisters.isEmpty()) {
// pass parameter via a regular variable (not via registers) val paramVar = parameter.value
val paramVar = parameter.value val scopedParamVar = (sub.scopedname+"."+paramVar.name).split(".")
val scopedParamVar = (sub.scopedname+"."+paramVar.name).split(".") val target = AssignTarget(null, IdentifierReference(scopedParamVar, sub.position), null, null, sub.position)
val target = AssignTarget(null, IdentifierReference(scopedParamVar, sub.position), null, null, sub.position) target.linkParents(value.parent)
target.linkParents(value.parent) when (value) {
when (value) { is NumericLiteralValue -> {
is NumericLiteralValue -> { // optimize when the argument is a constant literal
// optimize when the argument is a constant literal when(parameter.value.type) {
when(parameter.value.type) { in ByteDatatypes -> asmgen.assignFromByteConstant(target, value.number.toShort())
in ByteDatatypes -> asmgen.assignFromByteConstant(target, value.number.toShort()) in WordDatatypes -> asmgen.assignFromWordConstant(target, value.number.toInt())
in WordDatatypes -> asmgen.assignFromWordConstant(target, value.number.toInt()) DataType.FLOAT -> asmgen.assignFromFloatConstant(target, value.number.toDouble())
DataType.FLOAT -> asmgen.assignFromFloatConstant(target, value.number.toDouble()) in PassByReferenceDatatypes -> throw AssemblyError("can't pass string/array as argument via a variable?") // TODO huh
in PassByReferenceDatatypes -> throw AssemblyError("can't pass string/array as argument via a variable?") // TODO huh else -> throw AssemblyError("weird parameter datatype")
else -> throw AssemblyError("weird parameter datatype")
}
}
is IdentifierReference -> {
// optimize when the argument is a variable
when (parameter.value.type) {
in ByteDatatypes -> asmgen.assignFromByteVariable(target, value)
in WordDatatypes -> asmgen.assignFromWordVariable(target, value)
DataType.FLOAT -> asmgen.assignFromFloatVariable(target, value)
in PassByReferenceDatatypes -> throw AssemblyError("can't pass string/array as argument via a variable?") // TODO huh
else -> throw AssemblyError("weird parameter datatype")
}
}
is RegisterExpr -> {
asmgen.assignFromRegister(target, value.register)
}
is DirectMemoryRead -> {
when(value.addressExpression) {
is NumericLiteralValue -> {
val address = (value.addressExpression as NumericLiteralValue).number.toInt()
asmgen.assignFromMemoryByte(target, address, null)
}
is IdentifierReference -> {
asmgen.assignFromMemoryByte(target, null, value.addressExpression as IdentifierReference)
}
else -> {
asmgen.translateExpression(value.addressExpression)
asmgen.out(" jsr prog8_lib.read_byte_from_address | inx")
asmgen.assignFromRegister(target, Register.A)
}
}
}
else -> {
asmgen.translateExpression(value)
asmgen.assignFromEvalResult(target)
} }
} }
} else { is IdentifierReference -> {
// pass parameter via a register parameter // optimize when the argument is a variable
val paramRegister = sub.asmParameterRegisters[parameter.index] when (parameter.value.type) {
val statusflag = paramRegister.statusflag in ByteDatatypes -> asmgen.assignFromByteVariable(target, value)
val register = paramRegister.registerOrPair in WordDatatypes -> asmgen.assignFromWordVariable(target, value)
val stack = paramRegister.stack DataType.FLOAT -> asmgen.assignFromFloatVariable(target, value)
when { in PassByReferenceDatatypes -> throw AssemblyError("can't pass string/array as argument via a variable?") // TODO huh
stack -> { else -> throw AssemblyError("weird parameter datatype")
// push arg onto the stack
// note: argument order is reversed (first argument will be deepest on the stack)
asmgen.translateExpression(value)
} }
statusflag!=null -> { }
if (statusflag == Statusflag.Pc) { is RegisterExpr -> {
// this param needs to be set last, right before the jsr asmgen.assignFromRegister(target, value.register)
// for now, this is already enforced on the subroutine definition by the Ast Checker }
when(value) { is DirectMemoryRead -> {
is NumericLiteralValue -> { when(value.addressExpression) {
val carrySet = value.number.toInt() != 0 is NumericLiteralValue -> {
asmgen.out(if(carrySet) " sec" else " clc") val address = (value.addressExpression as NumericLiteralValue).number.toInt()
} asmgen.assignFromMemoryByte(target, address, null)
is IdentifierReference -> { }
val sourceName = asmgen.asmIdentifierName(value) is IdentifierReference -> {
asmgen.out(""" asmgen.assignFromMemoryByte(target, null, value.addressExpression as IdentifierReference)
}
else -> {
asmgen.translateExpression(value.addressExpression)
asmgen.out(" jsr prog8_lib.read_byte_from_address | inx")
asmgen.assignFromRegister(target, Register.A)
}
}
}
else -> {
asmgen.translateExpression(value)
asmgen.assignFromEvalResult(target)
}
}
}
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
val stack = paramRegister.stack
when {
stack -> {
// push arg onto the stack
// note: argument order is reversed (first argument will be deepest on the stack)
asmgen.translateExpression(value)
}
statusflag!=null -> {
if (statusflag == Statusflag.Pc) {
// this param needs to be set last, right before the jsr
// for now, this is already enforced on the subroutine definition by the Ast Checker
when(value) {
is NumericLiteralValue -> {
val carrySet = value.number.toInt() != 0
asmgen.out(if(carrySet) " sec" else " clc")
}
is IdentifierReference -> {
val sourceName = asmgen.asmIdentifierName(value)
asmgen.out("""
lda $sourceName lda $sourceName
beq + beq +
sec sec
@ -124,108 +174,108 @@ internal class FunctionCallAsmGen(private val program: Program, private val asmg
+ clc + clc
+ +
""") """)
}
is RegisterExpr -> {
when(value.register) {
Register.A -> asmgen.out(" cmp #0")
Register.X -> asmgen.out(" txa")
Register.Y -> asmgen.out(" tya")
} }
is RegisterExpr -> { asmgen.out("""
when(value.register) {
Register.A -> asmgen.out(" cmp #0")
Register.X -> asmgen.out(" txa")
Register.Y -> asmgen.out(" tya")
}
asmgen.out("""
beq + beq +
sec sec
bcs ++ bcs ++
+ clc + clc
+ +
""") """)
} }
else -> { else -> {
asmgen.translateExpression(value) asmgen.translateExpression(value)
asmgen.out(""" asmgen.out("""
inx inx
pha
lda $ESTACK_LO_HEX,x lda $ESTACK_LO_HEX,x
beq + beq +
sec sec
bcs ++ bcs ++
+ clc + clc
+ + pla
""") """)
}
}
}
else throw AssemblyError("can only use Carry as status flag parameter")
}
register!=null && register.name.length==1 -> {
when (value) {
is NumericLiteralValue -> {
val target = AssignTarget(Register.valueOf(register.name), null, null, null, sub.position)
target.linkParents(value.parent)
asmgen.assignFromByteConstant(target, value.number.toShort())
}
is IdentifierReference -> {
val target = AssignTarget(Register.valueOf(register.name), null, null, null, sub.position)
target.linkParents(value.parent)
asmgen.assignFromByteVariable(target, value)
}
else -> {
asmgen.translateExpression(value)
when(register) {
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")
else -> throw AssemblyError("cannot assign to register pair")
}
} }
} }
} }
register!=null && register.name.length==2 -> { else throw AssemblyError("can only use Carry as status flag parameter")
// register pair as a 16-bit value (only possible for subroutine parameters) }
when (value) { register!=null && register.name.length==1 -> {
is NumericLiteralValue -> { when (value) {
// optimize when the argument is a constant literal is NumericLiteralValue -> {
val hex = value.number.toHex() val target = AssignTarget(Register.valueOf(register.name), null, null, null, sub.position)
when (register) { target.linkParents(value.parent)
RegisterOrPair.AX -> asmgen.out(" lda #<$hex | ldx #>$hex") asmgen.assignFromByteConstant(target, value.number.toShort())
RegisterOrPair.AY -> asmgen.out(" lda #<$hex | ldy #>$hex") }
RegisterOrPair.XY -> asmgen.out(" ldx #<$hex | ldy #>$hex") is IdentifierReference -> {
else -> {} val target = AssignTarget(Register.valueOf(register.name), null, null, null, sub.position)
} target.linkParents(value.parent)
asmgen.assignFromByteVariable(target, value)
}
else -> {
asmgen.translateExpression(value)
when(register) {
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")
else -> throw AssemblyError("cannot assign to register pair")
} }
is AddressOf -> { }
// optimize when the argument is an address of something }
val sourceName = asmgen.asmIdentifierName(value.identifier) }
register!=null && register.name.length==2 -> {
// register pair as a 16-bit value (only possible for subroutine parameters)
when (value) {
is NumericLiteralValue -> {
// optimize when the argument is a constant literal
val hex = value.number.toHex()
when (register) {
RegisterOrPair.AX -> asmgen.out(" lda #<$hex | ldx #>$hex")
RegisterOrPair.AY -> asmgen.out(" lda #<$hex | ldy #>$hex")
RegisterOrPair.XY -> asmgen.out(" ldx #<$hex | ldy #>$hex")
else -> {}
}
}
is AddressOf -> {
// optimize when the argument is an address of something
val sourceName = asmgen.asmIdentifierName(value.identifier)
when (register) {
RegisterOrPair.AX -> asmgen.out(" lda #<$sourceName | ldx #>$sourceName")
RegisterOrPair.AY -> asmgen.out(" lda #<$sourceName | ldy #>$sourceName")
RegisterOrPair.XY -> asmgen.out(" ldx #<$sourceName | ldy #>$sourceName")
else -> {}
}
}
is IdentifierReference -> {
val sourceName = asmgen.asmIdentifierName(value)
if(valueDt in PassByReferenceDatatypes) {
when (register) { when (register) {
RegisterOrPair.AX -> asmgen.out(" lda #<$sourceName | ldx #>$sourceName") RegisterOrPair.AX -> asmgen.out(" lda #<$sourceName | ldx #>$sourceName")
RegisterOrPair.AY -> asmgen.out(" lda #<$sourceName | ldy #>$sourceName") RegisterOrPair.AY -> asmgen.out(" lda #<$sourceName | ldy #>$sourceName")
RegisterOrPair.XY -> asmgen.out(" ldx #<$sourceName | ldy #>$sourceName") RegisterOrPair.XY -> asmgen.out(" ldx #<$sourceName | ldy #>$sourceName")
else -> {} else -> {}
} }
} } else {
is IdentifierReference -> { when (register) {
val sourceName = asmgen.asmIdentifierName(value) RegisterOrPair.AX -> asmgen.out(" lda $sourceName | ldx $sourceName+1")
if(sourceDt in PassByReferenceDatatypes) { RegisterOrPair.AY -> asmgen.out(" lda $sourceName | ldy $sourceName+1")
when (register) { RegisterOrPair.XY -> asmgen.out(" ldx $sourceName | ldy $sourceName+1")
RegisterOrPair.AX -> asmgen.out(" lda #<$sourceName | ldx #>$sourceName") else -> {}
RegisterOrPair.AY -> asmgen.out(" lda #<$sourceName | ldy #>$sourceName")
RegisterOrPair.XY -> asmgen.out(" ldx #<$sourceName | ldy #>$sourceName")
else -> {}
}
} else {
when (register) {
RegisterOrPair.AX -> asmgen.out(" lda $sourceName | ldx $sourceName+1")
RegisterOrPair.AY -> asmgen.out(" lda $sourceName | ldy $sourceName+1")
RegisterOrPair.XY -> asmgen.out(" ldx $sourceName | ldy $sourceName+1")
else -> {}
}
} }
} }
else -> { }
asmgen.translateExpression(value) else -> {
if (register == RegisterOrPair.AX || register == RegisterOrPair.XY) asmgen.translateExpression(value)
throw AssemblyError("can't use X register here - use a variable") if (register == RegisterOrPair.AX || register == RegisterOrPair.XY)
else if (register == RegisterOrPair.AY) throw AssemblyError("can't use X register here - use a variable")
asmgen.out(" inx | lda $ESTACK_LO_HEX,x | ldy $ESTACK_HI_HEX,x") else if (register == RegisterOrPair.AY)
} asmgen.out(" inx | lda $ESTACK_LO_HEX,x | ldy $ESTACK_HI_HEX,x")
} }
} }
} }

View File

@ -2,9 +2,6 @@
TODO 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 - finalize (most) of the still missing "new" assignment asm code generation
- aliases for imported symbols for example perhaps '%alias print = c64scr.print' - 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) - 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 %option enable_floats
; TODO: fix register argument clobbering when calling asmsubs. ; TODO: optimize register arg value passing when there is no risk of register clobbering
; 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 { main {
sub start() { sub start() {
function(20, calculate()) function(20, calculate())
asmfunction(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') 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 { sub calculate() -> ubyte {
Y = 99 Y = 99
return Y return Y

View File

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

View File

@ -5,32 +5,8 @@
%option enable_floats %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 { main {
sub start() { 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
}
}