diff --git a/compiler/src/prog8/ast/processing/AstVariousTransforms.kt b/compiler/src/prog8/ast/processing/AstVariousTransforms.kt index a9658f73e..e40e201f6 100644 --- a/compiler/src/prog8/ast/processing/AstVariousTransforms.kt +++ b/compiler/src/prog8/ast/processing/AstVariousTransforms.kt @@ -10,44 +10,6 @@ import prog8.ast.statements.* internal class AstVariousTransforms(private val program: Program) : AstWalker() { private val noModifications = emptyList() - override fun after(functionCallStatement: FunctionCallStatement, parent: Node): Iterable { - if(functionCallStatement.target.nameInSource == listOf("swap")) { - // TODO don't replace swap(), let the code generator figure this all out - // if x and y are both just identifiers, do not rewrite (there should be asm generation for that) - // otherwise: - // rewrite swap(x,y) as follows: - // - declare a temp variable of the same datatype - // - temp = x, x = y, y= temp - val first = functionCallStatement.args[0] - val second = functionCallStatement.args[1] - if(first !is IdentifierReference && second !is IdentifierReference) { - val dt = first.inferType(program).typeOrElse(DataType.STRUCT) - val tempname = "prog8_swaptmp_${functionCallStatement.hashCode()}" - val tempvardecl = VarDecl(VarDeclType.VAR, dt, ZeropageWish.DONTCARE, null, tempname, null, null, isArray = false, autogeneratedDontRemove = true, position = first.position) - val tempvar = IdentifierReference(listOf(tempname), first.position) - val assignTemp = Assignment( - AssignTarget(tempvar, null, null, first.position), - first, - first.position - ) - val assignFirst = Assignment( - AssignTarget.fromExpr(first), - second, - first.position - ) - val assignSecond = Assignment( - AssignTarget.fromExpr(second), - tempvar, - first.position - ) - val scope = AnonymousScope(mutableListOf(tempvardecl, assignTemp, assignFirst, assignSecond), first.position) - return listOf(IAstModification.ReplaceNode(functionCallStatement, scope, parent)) - } - } - - return noModifications - } - override fun before(decl: VarDecl, parent: Node): Iterable { // is it a struct variable? then define all its struct members as mangled names, // and include the original decl as well. diff --git a/compiler/src/prog8/compiler/target/c64/codegen/BuiltinFunctionsAsmGen.kt b/compiler/src/prog8/compiler/target/c64/codegen/BuiltinFunctionsAsmGen.kt index 14c545211..4a2350446 100644 --- a/compiler/src/prog8/compiler/target/c64/codegen/BuiltinFunctionsAsmGen.kt +++ b/compiler/src/prog8/compiler/target/c64/codegen/BuiltinFunctionsAsmGen.kt @@ -4,8 +4,14 @@ import prog8.ast.IFunctionCall import prog8.ast.Program import prog8.ast.base.* import prog8.ast.expressions.* +import prog8.ast.statements.DirectMemoryWrite import prog8.ast.statements.FunctionCallStatement import prog8.compiler.AssemblyError +import prog8.compiler.target.c64.codegen.assignment.AsmAssignSource +import prog8.compiler.target.c64.codegen.assignment.AsmAssignTarget +import prog8.compiler.target.c64.codegen.assignment.AsmAssignment +import prog8.compiler.target.c64.codegen.assignment.SourceStorageKind +import prog8.compiler.target.c64.codegen.assignment.TargetStorageKind import prog8.compiler.toHex import prog8.functions.FSignature @@ -395,12 +401,14 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val private fun funcSwap(fcall: IFunctionCall) { val first = fcall.args[0] val second = fcall.args[1] + + // optimized simple case: swap two variables if(first is IdentifierReference && second is IdentifierReference) { val firstName = asmgen.asmVariableName(first) val secondName = asmgen.asmVariableName(second) val dt = first.inferType(program) if(dt.istype(DataType.BYTE) || dt.istype(DataType.UBYTE)) { - asmgen.out(" ldy $firstName | lda $secondName | sta $firstName | tya | sta $secondName") + asmgen.out(" ldy $firstName | lda $secondName | sta $firstName | sty $secondName") return } if(dt.istype(DataType.WORD) || dt.istype(DataType.UWORD)) { @@ -432,8 +440,190 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val } } - // other types of swap() calls should have been replaced by a different statement sequence involving a temp variable - throw AssemblyError("no asm generation for swap funccall $fcall") + // optimized simple case: swap two memory locations + if(first is DirectMemoryRead && second is DirectMemoryRead) { + val addr1 = (first.addressExpression as? NumericLiteralValue)?.number?.toHex() + val addr2 = (second.addressExpression as? NumericLiteralValue)?.number?.toHex() + val name1 = if(first.addressExpression is IdentifierReference) asmgen.asmVariableName(first.addressExpression as IdentifierReference) else null + val name2 = if(second.addressExpression is IdentifierReference) asmgen.asmVariableName(second.addressExpression as IdentifierReference) else null + + when { + addr1!=null && addr2!=null -> { + asmgen.out(" ldy $addr1 | lda $addr2 | sta $addr1 | sty $addr2") + return + } + addr1!=null && name2!=null -> { + asmgen.out(" ldy $addr1 | lda $name2 | sta $addr1 | sty $name2") + return + } + name1!=null && addr2 != null -> { + asmgen.out(" ldy $name1 | lda $addr2 | sta $name1 | sty $addr2") + return + } + name1!=null && name2!=null -> { + asmgen.out(" ldy $name1 | lda $name2 | sta $name1 | sty $name2") + return + } + } + } + + if(first is ArrayIndexedExpression && second is ArrayIndexedExpression) { + val indexValue1 = first.arrayspec.index as? NumericLiteralValue + val indexName1 = first.arrayspec.index as? IdentifierReference + val indexValue2 = second.arrayspec.index as? NumericLiteralValue + val indexName2 = second.arrayspec.index as? IdentifierReference + val arrayVarName1 = asmgen.asmVariableName(first.identifier) + val arrayVarName2 = asmgen.asmVariableName(second.identifier) + val elementDt = first.inferType(program).typeOrElse(DataType.STRUCT) + + if(indexValue1!=null && indexValue2!=null) { + swapArrayValues(elementDt, arrayVarName1, indexValue1, arrayVarName2, indexValue2) + return + } else if(indexName1!=null && indexName2!=null) { + swapArrayValues(elementDt, arrayVarName1, indexName1, arrayVarName2, indexName2) + return + } + } + + // all other types of swap() calls are done via the evaluation stack + fun targetFromExpr(expr: Expression, datatype: DataType): AsmAssignTarget { + return when (expr) { + is IdentifierReference -> AsmAssignTarget(TargetStorageKind.VARIABLE, program, asmgen, datatype, variable=expr) + is ArrayIndexedExpression -> AsmAssignTarget(TargetStorageKind.ARRAY, program, asmgen, datatype, array = expr) + is DirectMemoryRead -> AsmAssignTarget(TargetStorageKind.MEMORY, program, asmgen, datatype, memory = DirectMemoryWrite(expr.addressExpression, expr.position)) + else -> throw AssemblyError("invalid expression object $expr") + } + } + + asmgen.translateExpression(first) + asmgen.translateExpression(second) + val datatype = first.inferType(program).typeOrElse(DataType.STRUCT) + val assignFirst = AsmAssignment( + AsmAssignSource(SourceStorageKind.STACK, program, datatype), + targetFromExpr(first, datatype), + false, first.position + ) + val assignSecond = AsmAssignment( + AsmAssignSource(SourceStorageKind.STACK, program, datatype), + targetFromExpr(second, datatype), + false, second.position + ) + asmgen.translateNormalAssignment(assignFirst) + asmgen.translateNormalAssignment(assignSecond) + } + + private fun swapArrayValues(elementDt: DataType, arrayVarName1: String, indexValue1: NumericLiteralValue, arrayVarName2: String, indexValue2: NumericLiteralValue) { + val index1 = indexValue1.number.toInt() * elementDt.memorySize() + val index2 = indexValue2.number.toInt() * elementDt.memorySize() + when(elementDt) { + DataType.UBYTE, DataType.BYTE -> { + asmgen.out(""" + lda $arrayVarName1+$index1 + ldy $arrayVarName2+$index2 + sta $arrayVarName2+$index2 + sty $arrayVarName1+$index1 + """) + } + DataType.UWORD, DataType.WORD -> { + asmgen.out(""" + lda $arrayVarName1+$index1 + ldy $arrayVarName2+$index2 + sta $arrayVarName2+$index2 + sty $arrayVarName1+$index1 + lda $arrayVarName1+$index1+1 + ldy $arrayVarName2+$index2+1 + sta $arrayVarName2+$index2+1 + sty $arrayVarName1+$index1+1 + """) + } + DataType.FLOAT -> { + asmgen.out(""" + lda #<(${arrayVarName1}+$index1) + sta P8ZP_SCRATCH_W1 + lda #>(${arrayVarName1}+$index1) + sta P8ZP_SCRATCH_W1+1 + lda #<(${arrayVarName2}+$index2) + sta P8ZP_SCRATCH_W2 + lda #>(${arrayVarName2}+$index2) + sta P8ZP_SCRATCH_W2+1 + jsr c64flt.swap_floats + """) + } + else -> throw AssemblyError("invalid aray elt type") + } + } + + private fun swapArrayValues(elementDt: DataType, arrayVarName1: String, indexName1: IdentifierReference, arrayVarName2: String, indexName2: IdentifierReference) { + val idxAsmName1 = asmgen.asmVariableName(indexName1) + val idxAsmName2 = asmgen.asmVariableName(indexName2) + when(elementDt) { + DataType.UBYTE, DataType.BYTE -> { + asmgen.out(""" + stx P8ZP_SCRATCH_REG + ldx $idxAsmName1 + ldy $idxAsmName2 + lda $arrayVarName1,x + pha + lda $arrayVarName2,y + sta $arrayVarName1,x + pla + sta $arrayVarName2,y + ldx P8ZP_SCRATCH_REG + """) + } + DataType.UWORD, DataType.WORD -> { + asmgen.out(""" + stx P8ZP_SCRATCH_REG + lda $idxAsmName1 + asl a + tax + lda $idxAsmName2 + asl a + tay + lda $arrayVarName1,x + pha + lda $arrayVarName2,y + sta $arrayVarName1,x + pla + sta $arrayVarName2,y + lda $arrayVarName1+1,x + pha + lda $arrayVarName2+1,y + sta $arrayVarName1+1,x + pla + sta $arrayVarName2+1,y + ldx P8ZP_SCRATCH_REG + """) + } + DataType.FLOAT -> { + asmgen.out(""" + lda #>$arrayVarName1 + sta P8ZP_SCRATCH_W1+1 + lda $idxAsmName1 + asl a + asl a + clc + adc $idxAsmName1 + adc #<$arrayVarName1 + sta P8ZP_SCRATCH_W1 + bcc + + inc P8ZP_SCRATCH_W1+1 ++ lda #>$arrayVarName2 + sta P8ZP_SCRATCH_W2+1 + lda $idxAsmName2 + asl a + asl a + clc + adc $idxAsmName2 + adc #<$arrayVarName2 + sta P8ZP_SCRATCH_W2 + bcc + + inc P8ZP_SCRATCH_W2+1 ++ jsr c64flt.swap_floats + """) + } + else -> throw AssemblyError("invalid aray elt type") + } } private fun funcAbs(fcall: IFunctionCall, func: FSignature) { diff --git a/examples/test.p8 b/examples/test.p8 index 4ae589a67..9ea5492b4 100644 --- a/examples/test.p8 +++ b/examples/test.p8 @@ -1,7 +1,7 @@ %import c64lib %import c64graphics %import c64textio -;%import c64flt +%import c64flt ;%option enable_floats %zeropage basicsafe @@ -9,5 +9,65 @@ main { sub start() { + + ubyte i1=0 + ubyte i2=1 + + byte b1 = 11 + byte b2 = 22 + word w1 = 1111 + word w2 = 2222 + float f1 = 1.111 + float f2 = 2.222 + + byte[] barr = [1,2] + word[] warr = [1111,2222] + float[] farr= [1.111, 2.222] + + @($c000) = 11 + @($c001) = 22 + + swap(b1,b2) + swap(w1,w2) + swap(f1,f2) + swap(@($c000), @($c001)) + swap(barr[i1], barr[i2]) + swap(warr[i1], warr[i2]) + swap(farr[i1], farr[i2]) + + txt.print_b(b1) + c64.CHROUT(',') + txt.print_b(b2) + c64.CHROUT('\n') + + txt.print_w(w1) + c64.CHROUT(',') + txt.print_w(w2) + c64.CHROUT('\n') + + c64flt.print_f(f1) + c64.CHROUT(',') + c64flt.print_f(f2) + c64.CHROUT('\n') + + txt.print_b(barr[0]) + c64.CHROUT(',') + txt.print_b(barr[1]) + c64.CHROUT('\n') + + txt.print_w(warr[0]) + c64.CHROUT(',') + txt.print_w(warr[1]) + c64.CHROUT('\n') + + c64flt.print_f(farr[0]) + c64.CHROUT(',') + c64flt.print_f(farr[1]) + c64.CHROUT('\n') + + txt.print_ub(@($c000)) + c64.CHROUT(',') + txt.print_ub(@($c001)) + c64.CHROUT('\n') } }