diff --git a/compiler/src/prog8/compiler/target/c64/codegen/FunctionCallAsmGen.kt b/compiler/src/prog8/compiler/target/c64/codegen/FunctionCallAsmGen.kt index 186a2ef0b..619fea573 100644 --- a/compiler/src/prog8/compiler/target/c64/codegen/FunctionCallAsmGen.kt +++ b/compiler/src/prog8/compiler/target/c64/codegen/FunctionCallAsmGen.kt @@ -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()) { - for(arg in sub.parameters.withIndex().zip(stmt.args)) { - translateFuncArguments(arg.first, arg.second, sub) + if(sub.asmParameterRegisters.isEmpty()) { + // 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") @@ -35,88 +76,97 @@ internal class FunctionCallAsmGen(private val program: Program, private val asmg asmgen.out(" ldx c64.SCRATCH_ZPREGX") // restore X again } - private fun translateFuncArguments(parameter: IndexedValue, value: Expression, sub: Subroutine) { - val sourceIDt = value.inferType(program) - if(!sourceIDt.isKnown) + private fun argumentViaVariable(sub: Subroutine, parameter: IndexedValue, 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 sourceDt = sourceIDt.typeOrElse(DataType.STRUCT) - if(!argumentTypeCompatible(sourceDt, parameter.value.type)) + val valueDt = valueIDt.typeOrElse(DataType.STRUCT) + if(!argumentTypeCompatible(valueDt, parameter.value.type)) throw AssemblyError("argument type incompatible") - if(sub.asmParameterRegisters.isEmpty()) { - // pass parameter via a regular variable (not via registers) - val paramVar = parameter.value - val scopedParamVar = (sub.scopedname+"."+paramVar.name).split(".") - val target = AssignTarget(null, IdentifierReference(scopedParamVar, sub.position), null, null, sub.position) - target.linkParents(value.parent) - when (value) { - is NumericLiteralValue -> { - // optimize when the argument is a constant literal - when(parameter.value.type) { - in ByteDatatypes -> asmgen.assignFromByteConstant(target, value.number.toShort()) - in WordDatatypes -> asmgen.assignFromWordConstant(target, value.number.toInt()) - DataType.FLOAT -> asmgen.assignFromFloatConstant(target, value.number.toDouble()) - in PassByReferenceDatatypes -> throw AssemblyError("can't pass string/array as argument via a variable?") // TODO huh - 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) + + val paramVar = parameter.value + val scopedParamVar = (sub.scopedname+"."+paramVar.name).split(".") + val target = AssignTarget(null, IdentifierReference(scopedParamVar, sub.position), null, null, sub.position) + target.linkParents(value.parent) + when (value) { + is NumericLiteralValue -> { + // optimize when the argument is a constant literal + when(parameter.value.type) { + in ByteDatatypes -> asmgen.assignFromByteConstant(target, value.number.toShort()) + in WordDatatypes -> asmgen.assignFromWordConstant(target, value.number.toInt()) + DataType.FLOAT -> asmgen.assignFromFloatConstant(target, value.number.toDouble()) + in PassByReferenceDatatypes -> throw AssemblyError("can't pass string/array as argument via a variable?") // TODO huh + else -> throw AssemblyError("weird parameter datatype") } } - } else { - // pass parameter via a register parameter - 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) + 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") } - 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(""" + } + 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) + } + } + } + + private fun argumentViaRegister(sub: Subroutine, parameter: IndexedValue, 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 beq + sec @@ -124,108 +174,108 @@ internal class FunctionCallAsmGen(private val program: Program, private val asmg + clc + """) + } + is RegisterExpr -> { + when(value.register) { + Register.A -> asmgen.out(" cmp #0") + Register.X -> asmgen.out(" txa") + Register.Y -> asmgen.out(" tya") } - is RegisterExpr -> { - when(value.register) { - Register.A -> asmgen.out(" cmp #0") - Register.X -> asmgen.out(" txa") - Register.Y -> asmgen.out(" tya") - } - asmgen.out(""" + asmgen.out(""" beq + sec bcs ++ + clc + """) - } - else -> { - asmgen.translateExpression(value) - asmgen.out(""" - inx + } + else -> { + asmgen.translateExpression(value) + asmgen.out(""" + inx + pha lda $ESTACK_LO_HEX,x beq + sec bcs ++ + 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 -> { - // 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 -> {} - } + 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") } - 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) { 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(sourceDt in PassByReferenceDatatypes) { - 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 -> {} - } - } 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 { + 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) - if (register == RegisterOrPair.AX || register == RegisterOrPair.XY) - throw AssemblyError("can't use X register here - use a variable") - else if (register == RegisterOrPair.AY) - asmgen.out(" inx | lda $ESTACK_LO_HEX,x | ldy $ESTACK_HI_HEX,x") - } + } + else -> { + asmgen.translateExpression(value) + if (register == RegisterOrPair.AX || register == RegisterOrPair.XY) + throw AssemblyError("can't use X register here - use a variable") + else if (register == RegisterOrPair.AY) + asmgen.out(" inx | lda $ESTACK_LO_HEX,x | ldy $ESTACK_HI_HEX,x") } } } diff --git a/docs/source/todo.rst b/docs/source/todo.rst index db7cd9969..25a5d17f4 100644 --- a/docs/source/todo.rst +++ b/docs/source/todo.rst @@ -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) diff --git a/examples/fixme_argclobber.p8 b/examples/fixme_argclobber.p8 index e9fcfec45..f65ae5aeb 100644 --- a/examples/fixme_argclobber.p8 +++ b/examples/fixme_argclobber.p8 @@ -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 diff --git a/examples/tehtriz.p8 b/examples/tehtriz.p8 index 20873510d..53596ba90 100644 --- a/examples/tehtriz.p8 +++ b/examples/tehtriz.p8 @@ -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 } diff --git a/examples/test.p8 b/examples/test.p8 index 8475b9136..561358f78 100644 --- a/examples/test.p8 +++ b/examples/test.p8 @@ -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 - } - -}