From 248e7b808c612419992ef7545d6e1bf606c20e55 Mon Sep 17 00:00:00 2001 From: Irmen de Jong Date: Fri, 16 Aug 2019 22:49:29 +0200 Subject: [PATCH] split codegen --- .../compiler/target/c64/codegen/AsmGen.kt | 2111 +---------------- .../target/c64/codegen/AssignmentAsmGen.kt | 745 ++++++ .../c64/codegen/BuiltinFunctionsAsmGen.kt | 58 +- .../target/c64/codegen/ExpressionsAsmGen.kt | 430 ++++ .../target/c64/codegen/ForLoopsAsmGen.kt | 559 +++++ .../target/c64/codegen/FunctionCallAsmGen.kt | 235 ++ .../target/c64/codegen/PostIncrDecrAsmGen.kt | 149 ++ examples/cube3d-sprites.p8 | 2 + 8 files changed, 2197 insertions(+), 2092 deletions(-) create mode 100644 compiler/src/prog8/compiler/target/c64/codegen/AssignmentAsmGen.kt create mode 100644 compiler/src/prog8/compiler/target/c64/codegen/ExpressionsAsmGen.kt create mode 100644 compiler/src/prog8/compiler/target/c64/codegen/ForLoopsAsmGen.kt create mode 100644 compiler/src/prog8/compiler/target/c64/codegen/FunctionCallAsmGen.kt create mode 100644 compiler/src/prog8/compiler/target/c64/codegen/PostIncrDecrAsmGen.kt diff --git a/compiler/src/prog8/compiler/target/c64/codegen/AsmGen.kt b/compiler/src/prog8/compiler/target/c64/codegen/AsmGen.kt index a29164a36..9cb34c7d5 100644 --- a/compiler/src/prog8/compiler/target/c64/codegen/AsmGen.kt +++ b/compiler/src/prog8/compiler/target/c64/codegen/AsmGen.kt @@ -1,6 +1,5 @@ package prog8.compiler.target.c64.codegen -import prog8.ast.IFunctionCall import prog8.ast.Node import prog8.ast.Program import prog8.ast.antlr.escape @@ -10,14 +9,11 @@ import prog8.ast.statements.* import prog8.compiler.* import prog8.compiler.target.c64.AssemblyProgram import prog8.compiler.target.c64.MachineDefinition -import prog8.compiler.target.c64.MachineDefinition.C64Zeropage import prog8.compiler.target.c64.MachineDefinition.ESTACK_HI_HEX -import prog8.compiler.target.c64.MachineDefinition.ESTACK_HI_PLUS1_HEX import prog8.compiler.target.c64.MachineDefinition.ESTACK_LO_HEX -import prog8.compiler.target.c64.MachineDefinition.ESTACK_LO_PLUS1_HEX -import prog8.compiler.target.c64.MachineDefinition.ESTACK_LO_PLUS2_HEX import prog8.compiler.target.c64.Petscii import prog8.functions.BuiltinFunctions +import prog8.functions.FunctionSignature import java.io.File import java.math.RoundingMode import java.util.* @@ -35,9 +31,14 @@ internal class AsmGen(val program: Program, private val globalFloatConsts = mutableMapOf() // all float values in the entire program (value -> varname) private val allocatedZeropageVariables = mutableMapOf>() private val breakpointLabels = mutableListOf() - private val builtinFunctionsAsmGen = BuiltinFunctionsAsmGen(program, options, zeropage, this) - private val loopEndLabels = Stack() - private val loopContinueLabels = Stack() + private val builtinFunctionsAsmGen = BuiltinFunctionsAsmGen(program, this) + private val forloopsAsmGen = ForLoopsAsmGen(program, this) + private val postincrdecrAsmGen = PostIncrDecrAsmGen(program, this) + private val functioncallAsmGen = FunctionCallAsmGen(program, this) + private val assignmentAsmGen = AssignmentAsmGen(program, this) + private val expressionsAsmGen = ExpressionsAsmGen(program, this) + internal val loopEndLabels = Stack() + internal val loopContinueLabels = Stack() internal fun compileToAssembly(optimize: Boolean): AssemblyProgram { assemblyLines.clear() @@ -172,7 +173,7 @@ internal class AsmGen(val program: Program, private var generatedLabelSequenceNumber: Int = 0 - private fun makeLabel(postfix: String): String { + internal fun makeLabel(postfix: String): String { generatedLabelSequenceNumber++ return "_prog8_label_${generatedLabelSequenceNumber}_$postfix" } @@ -418,7 +419,7 @@ internal class AsmGen(val program: Program, } } - private fun getFloatConst(number: Double): String { + internal fun getFloatConst(number: Double): String { // try to match the ROM float constants to save memory val mflpt5 = MachineDefinition.Mflpt5.fromNumber(number) val floatbytes = shortArrayOf(mflpt5.b0, mflpt5.b1, mflpt5.b2, mflpt5.b3, mflpt5.b4) @@ -459,7 +460,7 @@ internal class AsmGen(val program: Program, } } - private fun signExtendAtoMsb(destination: String) = + internal fun signExtendAtoMsb(destination: String) = """ ora #$7f bmi + @@ -476,7 +477,7 @@ internal class AsmGen(val program: Program, return fixNameSymbols(name) } - private fun fixNameSymbols(name: String) = name.replace("<", "prog8_").replace(">", "") // take care of the autogenerated invalid (anon) label names + internal fun fixNameSymbols(name: String) = name.replace("<", "prog8_").replace(">", "") // take care of the autogenerated invalid (anon) label names private fun branchInstruction(condition: BranchCondition, complement: Boolean) = if(complement) { @@ -503,21 +504,7 @@ internal class AsmGen(val program: Program, } } - private fun argumentTypeCompatible(argType: DataType, paramType: DataType): Boolean { - if(argType isAssignableTo paramType) - return true - - // we have a special rule for some types. - // strings are assignable to UWORD, for example, and vice versa - if(argType in StringDatatypes && paramType==DataType.UWORD) - return true - if(argType==DataType.UWORD && paramType in StringDatatypes) - return true - - return false - } - - private fun readAndPushArrayvalueWithIndexA(arrayDt: DataType, variable: IdentifierReference) { + internal fun readAndPushArrayvalueWithIndexA(arrayDt: DataType, variable: IdentifierReference) { val variablename = asmIdentifierName(variable) when (arrayDt) { DataType.STR, DataType.STR_S, DataType.ARRAY_UB, DataType.ARRAY_B -> @@ -538,27 +525,7 @@ internal class AsmGen(val program: Program, } } - private fun popAndWriteArrayvalueWithIndexA(arrayDt: DataType, variablename: String) { - when (arrayDt) { - DataType.STR, DataType.STR_S, DataType.ARRAY_UB, DataType.ARRAY_B -> - out(" tay | inx | lda $ESTACK_LO_HEX,x | sta $variablename,y") - DataType.ARRAY_UW, DataType.ARRAY_W -> - out(" asl a | tay | inx | lda $ESTACK_LO_HEX,x | sta $variablename,y | lda $ESTACK_HI_HEX,x | sta $variablename+1,y") - DataType.ARRAY_F -> - // index * 5 is done in the subroutine that's called - out(""" - sta $ESTACK_LO_HEX,x - dex - lda #<$variablename - ldy #>$variablename - jsr c64flt.pop_float_to_indexed_var - """) - else -> - throw AssemblyError("weird array type") - } - } - - private fun saveRegister(register: Register) { + internal fun saveRegister(register: Register) { when(register) { Register.A -> out(" pha") Register.X -> out(" txa | pha") @@ -566,7 +533,7 @@ internal class AsmGen(val program: Program, } } - private fun restoreRegister(register: Register) { + internal fun restoreRegister(register: Register) { when(register) { Register.A -> out(" pla") Register.X -> out(" pla | tax") @@ -613,7 +580,7 @@ internal class AsmGen(val program: Program, if(builtinFunc!=null) { builtinFunctionsAsmGen.translateFunctioncallStatement(stmt, builtinFunc) } else { - translateSubroutineCall(stmt) + functioncallAsmGen.translateFunctionCall(stmt) // discard any results from the stack: val sub = stmt.target.targetSubroutine(program.namespace)!! val returns = sub.returntypes.zip(sub.asmReturnvaluesRegisters) @@ -625,13 +592,13 @@ internal class AsmGen(val program: Program, } } } - is Assignment -> translate(stmt) + is Assignment -> assignmentAsmGen.translate(stmt) is Jump -> translate(stmt) - is PostIncrDecr -> translate(stmt) + is PostIncrDecr -> postincrdecrAsmGen.translate(stmt) is Label -> translate(stmt) is BranchStatement -> translate(stmt) is IfStatement -> translate(stmt) - is ForLoop -> translate(stmt) + is ForLoop -> forloopsAsmGen.translate(stmt) is Continue -> out(" jmp ${loopContinueLabels.peek()}") is Break -> out(" jmp ${loopEndLabels.peek()}") is WhileLoop -> translate(stmt) @@ -643,215 +610,8 @@ internal class AsmGen(val program: Program, } } - private fun translateSubroutineCall(stmt: IFunctionCall) { - // output the code to setup the parameters and perform the actual call - // does NOT output the code to deal with the result values! - val sub = stmt.target.targetSubroutine(program.namespace) ?: throw AssemblyError("undefined subroutine ${stmt.target}") - if(Register.X in sub.asmClobbers) - out(" stx c64.SCRATCH_ZPREGX") // we only save X for now (required! is the eval stack pointer), screw A and Y... - - val subName = asmIdentifierName(stmt.target) - if(stmt.arglist.isNotEmpty()) { - for(arg in sub.parameters.withIndex().zip(stmt.arglist)) { - translateSubroutineArgument(arg.first, arg.second, sub) - } - } - out(" jsr $subName") - - if(Register.X in sub.asmClobbers) - out(" ldx c64.SCRATCH_ZPREGX") // restore X again - } - - fun translateSubroutineArgument(parameter: IndexedValue, 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()) { - // pass parameter via a variable - 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 -> assignFromByteConstant(target, value.number.toShort()) - in WordDatatypes -> assignFromWordConstant(target, value.number.toInt()) - DataType.FLOAT -> assignFromFloatConstant(target, value.number.toDouble()) - in PassByReferenceDatatypes -> throw AssemblyError("can't pass string/array as arguments?") - else -> throw AssemblyError("weird parameter datatype") - } - } - is IdentifierReference -> { - // optimize when the argument is a variable - when (parameter.value.type) { - in ByteDatatypes -> assignFromByteVariable(target, value) - in WordDatatypes -> assignFromWordVariable(target, value) - DataType.FLOAT -> assignFromFloatVariable(target, value) - in PassByReferenceDatatypes -> throw AssemblyError("can't pass string/array as arguments?") - else -> throw AssemblyError("weird parameter datatype") - } - } - is RegisterExpr -> { - assignFromRegister(target, value.register) - } - is DirectMemoryRead -> { - when(value.addressExpression) { - is NumericLiteralValue -> { - val address = (value.addressExpression as NumericLiteralValue).number.toInt() - assignFromMemoryByte(target, address, null) - } - is IdentifierReference -> { - assignFromMemoryByte(target, null, value.addressExpression as IdentifierReference) - } - else -> { - translateExpression(value.addressExpression) - out(" jsr prog8_lib.read_byte_from_address | inx") - assignFromRegister(target, Register.A) - } - } - } - else -> { - translateExpression(value) - assignFromEvalResult(target) - } - } - } 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) - 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 - out(if(carrySet) " sec" else " clc") - } - is IdentifierReference -> { - val sourceName = asmIdentifierName(value) - out(""" - lda $sourceName - beq + - sec - bcs ++ -+ clc -+ -""") - } - is RegisterExpr -> { - when(value.register) { - Register.A -> out(" cmp #0") - Register.X -> out(" txa") - Register.Y -> out(" tya") - } - out(""" - beq + - sec - bcs ++ -+ clc -+ -""") - } - else -> { - translateExpression(value) - out(""" - inx - lda $ESTACK_LO_HEX,x - beq + - sec - bcs ++ -+ clc -+ -""") - } - } - } - 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) - assignFromByteConstant(target, value.number.toShort()) - } - is IdentifierReference -> { - val target = AssignTarget(Register.valueOf(register.name), null, null, null, sub.position) - target.linkParents(value.parent) - assignFromByteVariable(target, value) - } - else -> { - translateExpression(value) - when(register) { - RegisterOrPair.A -> out(" inx | lda $ESTACK_LO_HEX,x") - RegisterOrPair.X -> throw AssemblyError("can't pop into X register - use a variable instead") - RegisterOrPair.Y -> 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 -> out(" lda #<$hex | ldx #>$hex") - RegisterOrPair.AY -> out(" lda #<$hex | ldy #>$hex") - RegisterOrPair.XY -> out(" ldx #<$hex | ldy #>$hex") - else -> {} - } - } - is AddressOf -> { - // optimize when the argument is an address of something - val sourceName = asmIdentifierName(value.identifier) - when (register) { - RegisterOrPair.AX -> out(" lda #<$sourceName | ldx #>$sourceName") - RegisterOrPair.AY -> out(" lda #<$sourceName | ldy #>$sourceName") - RegisterOrPair.XY -> out(" ldx #<$sourceName | ldy #>$sourceName") - else -> {} - } - } - is IdentifierReference -> { - val sourceName = asmIdentifierName(value) - when (register) { - RegisterOrPair.AX -> out(" lda $sourceName | ldx $sourceName+1") - RegisterOrPair.AY -> out(" lda $sourceName | ldy $sourceName+1") - RegisterOrPair.XY -> out(" ldx $sourceName | ldy $sourceName+1") - else -> {} - } - } - else -> { - 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) - out(" inx | lda $ESTACK_LO_HEX,x | ldy $ESTACK_HI_HEX,x") - } - } - } - } - } - } - private fun translate(stmt: IfStatement) { - translateExpression(stmt.condition) + expressionsAsmGen.translateExpression(stmt.condition) translateTestStack(stmt.condition.inferType(program).typeOrElse(DataType.STRUCT)) val elseLabel = makeLabel("if_else") val endLabel = makeLabel("if_end") @@ -879,7 +639,7 @@ internal class AsmGen(val program: Program, loopContinueLabels.push(whileLabel) out(whileLabel) // TODO optimize for the simple cases, can we avoid stack use? - translateExpression(stmt.condition) + expressionsAsmGen.translateExpression(stmt.condition) val conditionDt = stmt.condition.inferType(program) if(!conditionDt.isKnown) throw AssemblyError("unknown condition dt") @@ -909,7 +669,7 @@ internal class AsmGen(val program: Program, out(repeatLabel) // TODO optimize this for the simple cases, can we avoid stack use? translate(stmt.body) - translateExpression(stmt.untilCondition) + expressionsAsmGen.translateExpression(stmt.untilCondition) val conditionDt = stmt.untilCondition.inferType(program) if(!conditionDt.isKnown) throw AssemblyError("unknown condition dt") @@ -930,7 +690,7 @@ internal class AsmGen(val program: Program, } private fun translate(stmt: WhenStatement) { - translateExpression(stmt.condition) + expressionsAsmGen.translateExpression(stmt.condition) val endLabel = makeLabel("choice_end") val choiceBlocks = mutableListOf>() val conditionDt = stmt.condition.inferType(program) @@ -1036,683 +796,6 @@ internal class AsmGen(val program: Program, } } - private fun translate(stmt: ForLoop) { - val iterableDt = stmt.iterable.inferType(program) - if(!iterableDt.isKnown) - throw AssemblyError("can't determine iterable dt") - when(stmt.iterable) { - is RangeExpr -> { - val range = (stmt.iterable as RangeExpr).toConstantIntegerRange() - if(range==null) { - translateForOverNonconstRange(stmt, iterableDt.typeOrElse(DataType.STRUCT), stmt.iterable as RangeExpr) - } else { - if (range.isEmpty()) - throw AssemblyError("empty range") - translateForOverConstRange(stmt, iterableDt.typeOrElse(DataType.STRUCT), range) - } - } - is IdentifierReference -> { - translateForOverIterableVar(stmt, iterableDt.typeOrElse(DataType.STRUCT), stmt.iterable as IdentifierReference) - } - else -> throw AssemblyError("can't iterate over ${stmt.iterable}") - } - } - - private fun translateForOverNonconstRange(stmt: ForLoop, iterableDt: DataType, range: RangeExpr) { - val loopLabel = makeLabel("for_loop") - val endLabel = makeLabel("for_end") - val continueLabel = makeLabel("for_continue") - val counterLabel = makeLabel("for_counter") - loopEndLabels.push(endLabel) - loopContinueLabels.push(continueLabel) - val stepsize=range.step.constValue(program)?.number - when (stepsize) { - 1 -> { - when(iterableDt) { - DataType.ARRAY_B, DataType.ARRAY_UB -> { - if (stmt.loopRegister != null) { - // loop register over range - if(stmt.loopRegister!=Register.A) - throw AssemblyError("can only use A") - translateExpression(range.to) - translateExpression(range.from) - out(""" - inx - lda $ESTACK_LO_HEX,x - sta $loopLabel+1 - inx - lda $ESTACK_LO_HEX,x - sec - sbc $loopLabel+1 - adc #0 - sta $counterLabel -$loopLabel lda #0 ; modified""") - translate(stmt.body) - out(""" -$continueLabel dec $counterLabel - beq $endLabel - inc $loopLabel+1 - jmp $loopLabel -$counterLabel .byte 0 -$endLabel""") - } else { - // loop over byte range via loopvar - val varname = asmIdentifierName(stmt.loopVar!!) - translateExpression(range.to) - translateExpression(range.from) - out(""" - inx - lda $ESTACK_LO_HEX,x - sta $varname - inx - lda $ESTACK_LO_HEX,x - sec - sbc $varname - adc #0 - sta $counterLabel -$loopLabel""") - translate(stmt.body) - out(""" -$continueLabel dec $counterLabel - beq $endLabel - inc $varname - jmp $loopLabel -$counterLabel .byte 0 -$endLabel""") - } - } - DataType.ARRAY_UW, DataType.ARRAY_W -> { - translateExpression(range.to) - out(" inc $ESTACK_LO_HEX+1,x | bne + | inc $ESTACK_HI_HEX+1,x |+ ") - val varname = asmIdentifierName(stmt.loopVar!!) - val assignLoopvar = Assignment(AssignTarget(null, stmt.loopVar, null, null, stmt.loopVar!!.position), - null, range.from, range.position) - assignLoopvar.linkParents(stmt) - translate(assignLoopvar) - out(loopLabel) - translate(stmt.body) - out(""" - inc $varname - bne + - inc $varname+1 -+ lda $ESTACK_HI_HEX+1,x - cmp $varname+1 - bne + - lda $ESTACK_LO_HEX+1,x - cmp $varname - beq $endLabel -+ jmp $loopLabel -$endLabel inx""") - } - else -> throw AssemblyError("range expression can only be byte or word") - } - } - -1 -> { - when(iterableDt){ - DataType.ARRAY_B, DataType.ARRAY_UB -> { - if (stmt.loopRegister != null) { - // loop register over range - if(stmt.loopRegister!=Register.A) - throw AssemblyError("can only use A") - translateExpression(range.from) - translateExpression(range.to) - out(""" - inx - lda $ESTACK_LO_HEX+1,x - sta $loopLabel+1 - sec - sbc $ESTACK_LO_HEX,x - adc #0 - sta $counterLabel - inx -$loopLabel lda #0 ; modified""") - translate(stmt.body) - out(""" -$continueLabel dec $counterLabel - beq $endLabel - dec $loopLabel+1 - jmp $loopLabel -$counterLabel .byte 0 -$endLabel""") - } else { - // loop over byte range via loopvar - val varname = asmIdentifierName(stmt.loopVar!!) - translateExpression(range.from) - translateExpression(range.to) - out(""" - inx - lda $ESTACK_LO_HEX+1,x - sta $varname - sec - sbc $ESTACK_LO_HEX,x - adc #0 - sta $counterLabel - inx -$loopLabel""") - translate(stmt.body) - out(""" -$continueLabel dec $counterLabel - beq $endLabel - dec $varname - jmp $loopLabel -$counterLabel .byte 0 -$endLabel""") - } - } - DataType.ARRAY_UW, DataType.ARRAY_W -> { - translateExpression(range.to) - out(""" - lda $ESTACK_LO_HEX+1,x - bne + - dec $ESTACK_HI_HEX+1,x -+ dec $ESTACK_LO_HEX+1,x - """) - val varname = asmIdentifierName(stmt.loopVar!!) - val assignLoopvar = Assignment(AssignTarget(null, stmt.loopVar, null, null, stmt.loopVar!!.position), - null, range.from, range.position) - assignLoopvar.linkParents(stmt) - translate(assignLoopvar) - out(loopLabel) - translate(stmt.body) - out(""" - lda $varname - bne + - dec $varname+1 -+ dec $varname - lda $ESTACK_HI_HEX+1,x - cmp $varname+1 - bne + - lda $ESTACK_LO_HEX+1,x - cmp $varname - beq $endLabel -+ jmp $loopLabel -$endLabel inx""") - } - else -> throw AssemblyError("range expression can only be byte or word") - } - } - else -> when (iterableDt) { - DataType.ARRAY_UB, DataType.ARRAY_B -> TODO("non-const forloop bytes, step >1: $stepsize") - DataType.ARRAY_UW, DataType.ARRAY_W -> TODO("non-const forloop words, step >1: $stepsize") - else -> throw AssemblyError("range expression can only be byte or word") - } - } - loopEndLabels.pop() - loopContinueLabels.pop() - } - - private fun translateForOverIterableVar(stmt: ForLoop, iterableDt: DataType, ident: IdentifierReference) { - val loopLabel = makeLabel("for_loop") - val endLabel = makeLabel("for_end") - val continueLabel = makeLabel("for_continue") - loopEndLabels.push(endLabel) - loopContinueLabels.push(continueLabel) - val iterableName = asmIdentifierName(ident) - val decl = ident.targetVarDecl(program.namespace)!! - when(iterableDt) { - DataType.STR, DataType.STR_S -> { - if(stmt.loopRegister!=null && stmt.loopRegister!=Register.A) - throw AssemblyError("can only use A") - out(""" - lda #<$iterableName - ldy #>$iterableName - sta $loopLabel+1 - sty $loopLabel+2 -$loopLabel lda ${65535.toHex()} ; modified - beq $endLabel""") - if(stmt.loopVar!=null) - out(" sta ${asmIdentifierName(stmt.loopVar!!)}") - translate(stmt.body) - out(""" -$continueLabel inc $loopLabel+1 - bne $loopLabel - inc $loopLabel+2 - bne $loopLabel -$endLabel""") - } - DataType.ARRAY_UB, DataType.ARRAY_B -> { - val length = decl.arraysize!!.size()!! - if(stmt.loopRegister!=null && stmt.loopRegister!=Register.A) - throw AssemblyError("can only use A") - val counterLabel = makeLabel("for_counter") - val modifiedLabel = makeLabel("for_modified") - out(""" - lda #<$iterableName - ldy #>$iterableName - sta $modifiedLabel+1 - sty $modifiedLabel+2 - ldy #0 -$loopLabel sty $counterLabel -$modifiedLabel lda ${65535.toHex()},y ; modified""") - if(stmt.loopVar!=null) - out(" sta ${asmIdentifierName(stmt.loopVar!!)}") - translate(stmt.body) - out(""" -$continueLabel ldy $counterLabel - iny - cpy #${length and 255} - beq $endLabel - jmp $loopLabel -$counterLabel .byte 0 -$endLabel""") - } - DataType.ARRAY_W, DataType.ARRAY_UW -> { - val length = decl.arraysize!!.size()!! * 2 - if(stmt.loopRegister!=null) - throw AssemblyError("can't use register to loop over words") - val counterLabel = makeLabel("for_counter") - val modifiedLabel = makeLabel("for_modified") - val modifiedLabel2 = makeLabel("for_modified2") - val loopvarName = asmIdentifierName(stmt.loopVar!!) - out(""" - lda #<$iterableName - ldy #>$iterableName - sta $modifiedLabel+1 - sty $modifiedLabel+2 - lda #<$iterableName+1 - ldy #>$iterableName+1 - sta $modifiedLabel2+1 - sty $modifiedLabel2+2 - ldy #0 -$loopLabel sty $counterLabel -$modifiedLabel lda ${65535.toHex()},y ; modified - sta $loopvarName -$modifiedLabel2 lda ${65535.toHex()},y ; modified - sta $loopvarName+1""") - translate(stmt.body) - out(""" -$continueLabel ldy $counterLabel - iny - iny - cpy #${length and 255} - beq $endLabel - jmp $loopLabel -$counterLabel .byte 0 -$endLabel""") - } - DataType.ARRAY_F -> { - throw AssemblyError("for loop with floating point variables is not supported") - } - else -> throw AssemblyError("can't iterate over $iterableDt") - } - loopEndLabels.pop() - loopContinueLabels.pop() - } - - private fun translateForOverConstRange(stmt: ForLoop, iterableDt: DataType, range: IntProgression) { - // TODO: optimize loop code when the range is < 256 iterations, don't need a separate counter in such cases - val loopLabel = makeLabel("for_loop") - val endLabel = makeLabel("for_end") - val continueLabel = makeLabel("for_continue") - loopEndLabels.push(endLabel) - loopContinueLabels.push(continueLabel) - when(iterableDt) { - DataType.ARRAY_B, DataType.ARRAY_UB -> { - val counterLabel = makeLabel("for_counter") - if(stmt.loopRegister!=null) { - - // loop register over range - - if(stmt.loopRegister!=Register.A) - throw AssemblyError("can only use A") - when { - range.step==1 -> { - // step = 1 - out(""" - lda #${range.first} - sta $loopLabel+1 - lda #${range.last-range.first+1 and 255} - sta $counterLabel -$loopLabel lda #0 ; modified""") - translate(stmt.body) - out(""" -$continueLabel dec $counterLabel - beq $endLabel - inc $loopLabel+1 - jmp $loopLabel -$counterLabel .byte 0 -$endLabel""") - } - range.step==-1 -> { - // step = -1 - out(""" - lda #${range.first} - sta $loopLabel+1 - lda #${range.first-range.last+1 and 255} - sta $counterLabel -$loopLabel lda #0 ; modified """) - translate(stmt.body) - out(""" -$continueLabel dec $counterLabel - beq $endLabel - dec $loopLabel+1 - jmp $loopLabel -$counterLabel .byte 0 -$endLabel""") - } - range.step >= 2 -> { - // step >= 2 - out(""" - lda #${(range.last-range.first) / range.step + 1} - sta $counterLabel - lda #${range.first} -$loopLabel pha""") - translate(stmt.body) - out(""" -$continueLabel pla - dec $counterLabel - beq $endLabel - clc - adc #${range.step} - jmp $loopLabel -$counterLabel .byte 0 -$endLabel""") - } - else -> { - // step <= -2 - out(""" - lda #${(range.first-range.last) / range.step.absoluteValue + 1} - sta $counterLabel - lda #${range.first} -$loopLabel pha""") - translate(stmt.body) - out(""" -$continueLabel pla - dec $counterLabel - beq $endLabel - sec - sbc #${range.step.absoluteValue} - jmp $loopLabel -$counterLabel .byte 0 -$endLabel""") - } - } - - } else { - - // loop over byte range via loopvar - val varname = asmIdentifierName(stmt.loopVar!!) - when { - range.step==1 -> { - // step = 1 - out(""" - lda #${range.first} - sta $varname - lda #${range.last-range.first+1 and 255} - sta $counterLabel -$loopLabel""") - translate(stmt.body) - out(""" -$continueLabel dec $counterLabel - beq $endLabel - inc $varname - jmp $loopLabel -$counterLabel .byte 0 -$endLabel""") - } - range.step==-1 -> { - // step = -1 - out(""" - lda #${range.first} - sta $varname - lda #${range.first-range.last+1 and 255} - sta $counterLabel -$loopLabel""") - translate(stmt.body) - out(""" -$continueLabel dec $counterLabel - beq $endLabel - dec $varname - jmp $loopLabel -$counterLabel .byte 0 -$endLabel""") - } - range.step >= 2 -> { - // step >= 2 - out(""" - lda #${(range.last-range.first) / range.step + 1} - sta $counterLabel - lda #${range.first} - sta $varname -$loopLabel""") - translate(stmt.body) - out(""" -$continueLabel dec $counterLabel - beq $endLabel - lda $varname - clc - adc #${range.step} - sta $varname - jmp $loopLabel -$counterLabel .byte 0 -$endLabel""") - } - else -> { - // step <= -2 - out(""" - lda #${(range.first-range.last) / range.step.absoluteValue + 1} - sta $counterLabel - lda #${range.first} - sta $varname -$loopLabel""") - translate(stmt.body) - out(""" -$continueLabel dec $counterLabel - beq $endLabel - lda $varname - sec - sbc #${range.step.absoluteValue} - sta $varname - jmp $loopLabel -$counterLabel .byte 0 -$endLabel""") - } - } - } - } - DataType.ARRAY_W, DataType.ARRAY_UW -> { - // loop over word range via loopvar - val varname = asmIdentifierName(stmt.loopVar!!) - when { - range.step == 1 -> { - // word, step = 1 - val lastValue = range.last+1 - out(""" - lda #<${range.first} - ldy #>${range.first} - sta $varname - sty $varname+1 -$loopLabel""") - translate(stmt.body) - out(""" -$continueLabel inc $varname - bne + - inc $varname+1 -+ lda $varname - cmp #<$lastValue - bne + - lda $varname+1 - cmp #>$lastValue - beq $endLabel -+ jmp $loopLabel -$endLabel""") - } - range.step == -1 -> { - // word, step = 1 - val lastValue = range.last-1 - out(""" - lda #<${range.first} - ldy #>${range.first} - sta $varname - sty $varname+1 -$loopLabel""") - translate(stmt.body) - out(""" -$continueLabel lda $varname - bne + - dec $varname+1 -+ dec $varname - lda $varname - cmp #<$lastValue - bne + - lda $varname+1 - cmp #>$lastValue - beq $endLabel -+ jmp $loopLabel -$endLabel""") - } - range.step >= 2 -> { - // word, step >= 2 - TODO("for, word, step>=2") - } - else -> { - // step <= -2 - TODO("for, word, step<=-2") - } - } - } - else -> throw AssemblyError("range expression can only be byte or word") - } - loopEndLabels.pop() - loopContinueLabels.pop() - } - - private fun translate(stmt: PostIncrDecr) { - val incr = stmt.operator=="++" - val targetIdent = stmt.target.identifier - val targetMemory = stmt.target.memoryAddress - val targetArrayIdx = stmt.target.arrayindexed - val targetRegister = stmt.target.register - when { - targetRegister!=null -> { - when(targetRegister) { - Register.A -> { - if(incr) - out(" clc | adc #1 ") - else - out(" sec | sbc #1 ") - } - Register.X -> { - if(incr) out(" inx") else out(" dex") - } - Register.Y -> { - if(incr) out(" iny") else out(" dey") - } - } - } - targetIdent!=null -> { - val what = asmIdentifierName(targetIdent) - val dt = stmt.target.inferType(program, stmt).typeOrElse(DataType.STRUCT) - when (dt) { - in ByteDatatypes -> out(if (incr) " inc $what" else " dec $what") - in WordDatatypes -> { - if(incr) - out(" inc $what | bne + | inc $what+1 |+") - else - out(""" - lda $what - bne + - dec $what+1 -+ dec $what -""") - } - DataType.FLOAT -> { - out(" lda #<$what | ldy #>$what") - out(if(incr) " jsr c64flt.inc_var_f" else " jsr c64flt.dec_var_f") - } - else -> throw AssemblyError("need numeric type") - } - } - targetMemory!=null -> { - val addressExpr = targetMemory.addressExpression - when (addressExpr) { - is NumericLiteralValue -> { - val what = addressExpr.number.toHex() - out(if(incr) " inc $what" else " dec $what") - } - is IdentifierReference -> { - val what = asmIdentifierName(addressExpr) - out(if(incr) " inc $what" else " dec $what") - } - else -> throw AssemblyError("weird target type $targetMemory") - } - } - targetArrayIdx!=null -> { - val index = targetArrayIdx.arrayspec.index - val what = asmIdentifierName(targetArrayIdx.identifier) - val arrayDt = targetArrayIdx.identifier.inferType(program).typeOrElse(DataType.STRUCT) - val elementDt = ArrayElementTypes.getValue(arrayDt) - when(index) { - is NumericLiteralValue -> { - val indexValue = index.number.toInt() * elementDt.memorySize() - when(elementDt) { - in ByteDatatypes -> out(if (incr) " inc $what+$indexValue" else " dec $what+$indexValue") - in WordDatatypes -> { - if(incr) - out(" inc $what+$indexValue | bne + | inc $what+$indexValue+1 |+") - else - out(""" - lda $what+$indexValue - bne + - dec $what+$indexValue+1 -+ dec $what+$indexValue -""") - } - DataType.FLOAT -> { - out(" lda #<$what+$indexValue | ldy #>$what+$indexValue") - out(if(incr) " jsr c64flt.inc_var_f" else " jsr c64flt.dec_var_f") - } - else -> throw AssemblyError("need numeric type") - } - } - is RegisterExpr -> { - // TODO optimize common cases - translateArrayIndexIntoA(targetArrayIdx) - incrDecrArrayvalueWithIndexA(incr, arrayDt, what) - } - is IdentifierReference -> { - // TODO optimize common cases - translateArrayIndexIntoA(targetArrayIdx) - incrDecrArrayvalueWithIndexA(incr, arrayDt, what) - } - else -> { - // TODO optimize common cases - translateArrayIndexIntoA(targetArrayIdx) - incrDecrArrayvalueWithIndexA(incr, arrayDt, what) - } - } - } - else -> throw AssemblyError("weird target type ${stmt.target}") - } - } - - private fun incrDecrArrayvalueWithIndexA(incr: Boolean, arrayDt: DataType, arrayVarName: String) { - out(" stx ${C64Zeropage.SCRATCH_REG_X} | tax") - when(arrayDt) { - DataType.STR, DataType.STR_S, - DataType.ARRAY_UB, DataType.ARRAY_B -> { - out(if(incr) " inc $arrayVarName,x" else " dec $arrayVarName,x") - } - DataType.ARRAY_UW, DataType.ARRAY_W -> { - if(incr) - out(" inc $arrayVarName,x | bne + | inc $arrayVarName+1,x |+") - else - out(""" - lda $arrayVarName,x - bne + - dec $arrayVarName+1,x -+ dec $arrayVarName -""") - } - DataType.ARRAY_F -> { - out(" lda #<$arrayVarName | ldy #>$arrayVarName") - out(if(incr) " jsr c64flt.inc_indexed_var_f" else " jsr c64flt.dec_indexed_var_f") - } - else -> throw AssemblyError("weird array dt") - } - out(" ldx ${C64Zeropage.SCRATCH_REG_X}") - } - private fun translate(jmp: Jump) { out(" jmp ${getJumpTarget(jmp)}") } @@ -1727,7 +810,7 @@ $endLabel""") } private fun translate(ret: Return) { - ret.value?.let { translateExpression(it) } + ret.value?.let { expressionsAsmGen.translateExpression(it) } out(" rts") } @@ -1736,113 +819,7 @@ $endLabel""") assemblyLines.add(assembly) } - private fun translate(assign: Assignment) { - if(assign.aug_op!=null) - throw AssemblyError("aug-op assignments should have been transformed to normal ones") - - when(assign.value) { - is NumericLiteralValue -> { - val numVal = assign.value as NumericLiteralValue - when(numVal.type) { - DataType.UBYTE, DataType.BYTE -> assignFromByteConstant(assign.target, numVal.number.toShort()) - DataType.UWORD, DataType.WORD -> assignFromWordConstant(assign.target, numVal.number.toInt()) - DataType.FLOAT -> assignFromFloatConstant(assign.target, numVal.number.toDouble()) - else -> throw AssemblyError("weird numval type") - } - } - is RegisterExpr -> { - assignFromRegister(assign.target, (assign.value as RegisterExpr).register) - } - is IdentifierReference -> { - val type = assign.target.inferType(program, assign).typeOrElse(DataType.STRUCT) - when(type) { - DataType.UBYTE, DataType.BYTE -> assignFromByteVariable(assign.target, assign.value as IdentifierReference) - DataType.UWORD, DataType.WORD -> assignFromWordVariable(assign.target, assign.value as IdentifierReference) - DataType.FLOAT -> assignFromFloatVariable(assign.target, assign.value as IdentifierReference) - else -> throw AssemblyError("unsupported assignment target type $type") - } - } - is AddressOf -> { - val identifier = (assign.value as AddressOf).identifier - assignFromAddressOf(assign.target, identifier) - } - is DirectMemoryRead -> { - val read = (assign.value as DirectMemoryRead) - when(read.addressExpression) { - is NumericLiteralValue -> { - val address = (read.addressExpression as NumericLiteralValue).number.toInt() - assignFromMemoryByte(assign.target, address, null) - } - is IdentifierReference -> { - assignFromMemoryByte(assign.target, null, read.addressExpression as IdentifierReference) - } - else -> { - translateExpression(read.addressExpression) - TODO("read memory byte from result and put that in ${assign.target}") - } - } - } - is PrefixExpression -> { - // TODO optimize common cases - translateExpression(assign.value as PrefixExpression) - assignFromEvalResult(assign.target) - } - is BinaryExpression -> { - // TODO optimize common cases - translateExpression(assign.value as BinaryExpression) - assignFromEvalResult(assign.target) - } - is ArrayIndexedExpression -> { - // TODO optimize common cases - val arrayExpr = assign.value as ArrayIndexedExpression - val arrayDt = arrayExpr.identifier.targetVarDecl(program.namespace)!!.datatype - val index = arrayExpr.arrayspec.index - if(index is NumericLiteralValue) { - // constant array index value - val arrayVarName = asmIdentifierName(arrayExpr.identifier) - val indexValue = index.number.toInt() * ArrayElementTypes.getValue(arrayDt).memorySize() - when (arrayDt) { - DataType.STR, DataType.STR_S, DataType.ARRAY_UB, DataType.ARRAY_B -> - out(" lda $arrayVarName+$indexValue | sta $ESTACK_LO_HEX,x | dex") - DataType.ARRAY_UW, DataType.ARRAY_W -> - out(" lda $arrayVarName+$indexValue | sta $ESTACK_LO_HEX,x | lda $arrayVarName+$indexValue+1 | sta $ESTACK_HI_HEX,x | dex") - DataType.ARRAY_F -> - out(" lda #<$arrayVarName+$indexValue | ldy #>$arrayVarName+$indexValue | jsr c64flt.push_float") - else -> - throw AssemblyError("weird array type") - } - } else { - translateArrayIndexIntoA(arrayExpr) - readAndPushArrayvalueWithIndexA(arrayDt, arrayExpr.identifier) - } - assignFromEvalResult(assign.target) - } - is TypecastExpression -> { - val cast = assign.value as TypecastExpression - val sourceType = cast.expression.inferType(program) - val targetType = assign.target.inferType(program, assign) - if(sourceType.isKnown && targetType.isKnown && - (sourceType.typeOrElse(DataType.STRUCT) in ByteDatatypes && targetType.typeOrElse(DataType.STRUCT) in ByteDatatypes) || - (sourceType.typeOrElse(DataType.STRUCT) in WordDatatypes && targetType.typeOrElse(DataType.STRUCT) in WordDatatypes)) { - // no need for a type cast - assign.value = cast.expression - translate(assign) - } else { - translateExpression(assign.value as TypecastExpression) - assignFromEvalResult(assign.target) - } - } - is FunctionCall -> { - translateExpression(assign.value as FunctionCall) - assignFromEvalResult(assign.target) - } - is ArrayLiteralValue, is StringLiteralValue -> TODO("string/array/struct assignment?") - is StructLiteralValue -> throw AssemblyError("struct literal value assignment should have been flattened") - is RangeExpr -> throw AssemblyError("range expression should have been changed into array values") - } - } - - private fun translateArrayIndexIntoA(expr: ArrayIndexedExpression) { + internal fun translateArrayIndexIntoA(expr: ArrayIndexedExpression) { val index = expr.arrayspec.index when (index) { is NumericLiteralValue -> throw AssemblyError("this should be optimized directly") @@ -1858,1031 +835,45 @@ $endLabel""") out(" lda $indexName") } else -> { - translateExpression(index) + expressionsAsmGen.translateExpression(index) out(" inx | lda $ESTACK_LO_HEX,x") } } } - private fun translateExpression(expr: TypecastExpression) { - translateExpression(expr.expression) - when(expr.expression.inferType(program).typeOrElse(DataType.STRUCT)) { - DataType.UBYTE -> { - when(expr.type) { - DataType.UBYTE, DataType.BYTE -> {} - DataType.UWORD, DataType.WORD -> out(" lda #0 | sta $ESTACK_HI_PLUS1_HEX,x") - DataType.FLOAT -> out(" jsr c64flt.stack_ub2float") - in PassByReferenceDatatypes -> throw AssemblyError("cannot cast to a pass-by-reference datatype") - else -> throw AssemblyError("weird type") - } - } - DataType.BYTE -> { - when(expr.type) { - DataType.UBYTE, DataType.BYTE -> {} - DataType.UWORD, DataType.WORD -> out(" lda $ESTACK_LO_PLUS1_HEX,x | ${signExtendAtoMsb("$ESTACK_HI_PLUS1_HEX,x")}") - DataType.FLOAT -> out(" jsr c64flt.stack_b2float") - in PassByReferenceDatatypes -> throw AssemblyError("cannot cast to a pass-by-reference datatype") - else -> throw AssemblyError("weird type") - } - } - DataType.UWORD -> { - when(expr.type) { - DataType.BYTE, DataType.UBYTE -> {} - DataType.WORD, DataType.UWORD -> {} - DataType.FLOAT -> out(" jsr c64flt.stack_uw2float") - in PassByReferenceDatatypes -> throw AssemblyError("cannot cast to a pass-by-reference datatype") - else -> throw AssemblyError("weird type") - } - } - DataType.WORD -> { - when(expr.type) { - DataType.BYTE, DataType.UBYTE -> {} - DataType.WORD, DataType.UWORD -> {} - DataType.FLOAT -> out(" jsr c64flt.stack_w2float") - in PassByReferenceDatatypes -> throw AssemblyError("cannot cast to a pass-by-reference datatype") - else -> throw AssemblyError("weird type") - } - } - DataType.FLOAT -> { - when(expr.type) { - DataType.UBYTE -> out(" jsr c64flt.stack_float2uw") - DataType.BYTE -> out(" jsr c64flt.stack_float2w") - DataType.UWORD -> out(" jsr c64flt.stack_float2uw") - DataType.WORD -> out(" jsr c64flt.stack_float2w") - DataType.FLOAT -> {} - in PassByReferenceDatatypes -> throw AssemblyError("cannot cast to a pass-by-reference datatype") - else -> throw AssemblyError("weird type") - } - } - in PassByReferenceDatatypes -> throw AssemblyError("cannot case a pass-by-reference datatypes into something else") - else -> throw AssemblyError("weird type") - } - } + internal fun translateExpression(expression: Expression) = + expressionsAsmGen.translateExpression(expression) - internal fun translateExpression(expression: Expression) { - when(expression) { - is PrefixExpression -> translateExpression(expression) - is BinaryExpression -> translateExpression(expression) - is ArrayIndexedExpression -> translatePushFromArray(expression) - is TypecastExpression -> translateExpression(expression) - is AddressOf -> translateExpression(expression) - is DirectMemoryRead -> translateExpression(expression) - is NumericLiteralValue -> translateExpression(expression) - is RegisterExpr -> translateExpression(expression) - is IdentifierReference -> translateExpression(expression) - is FunctionCall -> { - val functionName = expression.target.nameInSource.last() - val builtinFunc = BuiltinFunctions[functionName] - if(builtinFunc!=null) { - builtinFunctionsAsmGen.translateFunctioncallExpression(expression, builtinFunc) - } else { - translateSubroutineCall(expression) - val sub = expression.target.targetSubroutine(program.namespace)!! - val returns = sub.returntypes.zip(sub.asmReturnvaluesRegisters) - for((_, reg) in returns) { - if(!reg.stack) { - // result value in cpu or status registers, put it on the stack - if(reg.registerOrPair!=null) { - when(reg.registerOrPair) { - RegisterOrPair.A -> out(" sta $ESTACK_LO_HEX,x | dex") - RegisterOrPair.Y -> out(" tya | sta $ESTACK_LO_HEX,x | dex") - RegisterOrPair.AY -> out(" sta $ESTACK_LO_HEX,x | tya | sta $ESTACK_HI_HEX,x | dex") - RegisterOrPair.X, RegisterOrPair.AX, RegisterOrPair.XY -> throw AssemblyError("can't push X register - use a variable") - } - } - // return value from a statusregister is not put on the stack, it should be acted on via a conditional branch such as if_cc - } - } - } - } - is ArrayLiteralValue, is StringLiteralValue -> TODO("string/array/struct assignment?") - is StructLiteralValue -> throw AssemblyError("struct literal value assignment should have been flattened") - is RangeExpr -> throw AssemblyError("range expression should have been changed into array values") - } - } + internal fun translateFunctioncallExpression(functionCall: FunctionCall, signature: FunctionSignature) = + builtinFunctionsAsmGen.translateFunctioncallExpression(functionCall, signature) - private fun translatePushFromArray(arrayExpr: ArrayIndexedExpression) { - // assume *reading* from an array - val index = arrayExpr.arrayspec.index - val arrayDt = arrayExpr.identifier.targetVarDecl(program.namespace)!!.datatype - val arrayVarName = asmIdentifierName(arrayExpr.identifier) - if(index is NumericLiteralValue) { - val elementDt = ArrayElementTypes.getValue(arrayDt) - val indexValue = index.number.toInt() * elementDt.memorySize() - when(elementDt) { - in ByteDatatypes -> { - out(" lda $arrayVarName+$indexValue | sta $ESTACK_LO_HEX,x | dex") - } - in WordDatatypes -> { - out(" lda $arrayVarName+$indexValue | sta $ESTACK_LO_HEX,x | lda $arrayVarName+$indexValue+1 | sta $ESTACK_HI_HEX,x | dex") - } - DataType.FLOAT -> { - out(" lda #<$arrayVarName+$indexValue | ldy #>$arrayVarName+$indexValue | jsr c64flt.push_float") - } - else -> throw AssemblyError("weird type") - } - } else { - translateArrayIndexIntoA(arrayExpr) - readAndPushArrayvalueWithIndexA(arrayDt, arrayExpr.identifier) - } - } + internal fun translateFunctionCall(functionCall: FunctionCall) = + functioncallAsmGen.translateFunctionCall(functionCall) - private fun translateExpression(expr: AddressOf) { - val name = asmIdentifierName(expr.identifier) - out(" lda #<$name | sta $ESTACK_LO_HEX,x | lda #>$name | sta $ESTACK_HI_HEX,x | dex") - } + internal fun assignFromEvalResult(target: AssignTarget) = + assignmentAsmGen.assignFromEvalResult(target) - private fun translateExpression(expr: DirectMemoryRead) { - when(expr.addressExpression) { - is NumericLiteralValue -> { - val address = (expr.addressExpression as NumericLiteralValue).number.toInt() - out(" lda ${address.toHex()} | sta $ESTACK_LO_HEX,x | dex") - } - is IdentifierReference -> { - val sourceName = asmIdentifierName(expr.addressExpression as IdentifierReference) - out(" lda $sourceName | sta $ESTACK_LO_HEX,x | dex") - } - else -> { - translateExpression(expr.addressExpression) - out(" jsr prog8_lib.read_byte_from_address") - out(" sta $ESTACK_LO_PLUS1_HEX,x") - } - } - } + fun assignFromByteConstant(target: AssignTarget, value: Short) = + assignmentAsmGen.assignFromByteConstant(target, value) - private fun translateExpression(expr: NumericLiteralValue) { - when(expr.type) { - DataType.UBYTE, DataType.BYTE -> out(" lda #${expr.number.toHex()} | sta $ESTACK_LO_HEX,x | dex") - DataType.UWORD, DataType.WORD -> out(""" - lda #<${expr.number.toHex()} - sta $ESTACK_LO_HEX,x - lda #>${expr.number.toHex()} - sta $ESTACK_HI_HEX,x - dex - """) - DataType.FLOAT -> { - val floatConst = getFloatConst(expr.number.toDouble()) - out(" lda #<$floatConst | ldy #>$floatConst | jsr c64flt.push_float") - } - else -> throw AssemblyError("weird type") - } - } + fun assignFromWordConstant(target: AssignTarget, value: Int) = + assignmentAsmGen.assignFromWordConstant(target, value) - private fun translateExpression(expr: RegisterExpr) { - when(expr.register) { - Register.A -> out(" sta $ESTACK_LO_HEX,x | dex") - Register.X -> throw AssemblyError("cannot push X - use a variable instead of the X register") - Register.Y -> out(" tya | sta $ESTACK_LO_HEX,x | dex") - } - } + fun assignFromFloatConstant(target: AssignTarget, value: Double) = + assignmentAsmGen.assignFromFloatConstant(target, value) - private fun translateExpression(expr: IdentifierReference) { - val varname = asmIdentifierName(expr) - when(expr.inferType(program).typeOrElse(DataType.STRUCT)) { - DataType.UBYTE, DataType.BYTE -> { - out(" lda $varname | sta $ESTACK_LO_HEX,x | dex") - } - DataType.UWORD, DataType.WORD, in ArrayDatatypes, in StringDatatypes -> { - // (for arrays and strings, push their address) - out(" lda $varname | sta $ESTACK_LO_HEX,x | lda $varname+1 | sta $ESTACK_HI_HEX,x | dex") - } - DataType.FLOAT -> { - out(" lda #<$varname | ldy #>$varname| jsr c64flt.push_float") - } - else -> throw AssemblyError("stack push weird variable type $expr") - } - } + fun assignFromByteVariable(target: AssignTarget, variable: IdentifierReference) = + assignmentAsmGen.assignFromByteVariable(target, variable) - private val optimizedByteMultiplications = setOf(3,5,6,7,9,10,11,12,13,14,15,20,25,40) - private val optimizedWordMultiplications = setOf(3,5,6,7,9,10,12,15,20,25,40) - private val powerOfTwos = setOf(0,1,2,4,8,16,32,64,128,256) + fun assignFromWordVariable(target: AssignTarget, variable: IdentifierReference) = + assignmentAsmGen.assignFromWordVariable(target, variable) - private fun translateExpression(expr: BinaryExpression) { - val leftIDt = expr.left.inferType(program) - val rightIDt = expr.right.inferType(program) - if(!leftIDt.isKnown || !rightIDt.isKnown) - throw AssemblyError("can't infer type of both expression operands") + fun assignFromFloatVariable(target: AssignTarget, variable: IdentifierReference) = + assignmentAsmGen.assignFromFloatVariable(target, variable) - val leftDt = leftIDt.typeOrElse(DataType.STRUCT) - val rightDt = rightIDt.typeOrElse(DataType.STRUCT) - // see if we can apply some optimized routines - when(expr.operator) { - ">>" -> { - // bit-shifts are always by a constant number (for now) - translateExpression(expr.left) - val amount = expr.right.constValue(program)!!.number.toInt() - when (leftDt) { - DataType.UBYTE -> repeat(amount) { out(" lsr $ESTACK_LO_PLUS1_HEX,x") } - DataType.BYTE -> repeat(amount) { out(" lda $ESTACK_LO_PLUS1_HEX,x | asl a | ror $ESTACK_LO_PLUS1_HEX,x") } - DataType.UWORD -> repeat(amount) { out(" lsr $ESTACK_HI_PLUS1_HEX,x | ror $ESTACK_LO_PLUS1_HEX,x") } - DataType.WORD -> repeat(amount) { out(" lda $ESTACK_HI_PLUS1_HEX,x | asl a | ror $ESTACK_HI_PLUS1_HEX,x | ror $ESTACK_LO_PLUS1_HEX,x") } - else -> throw AssemblyError("weird type") - } - return - } - "<<" -> { - // bit-shifts are always by a constant number (for now) - translateExpression(expr.left) - val amount = expr.right.constValue(program)!!.number.toInt() - if (leftDt in ByteDatatypes) - repeat(amount) { out(" asl $ESTACK_LO_PLUS1_HEX,x") } - else - repeat(amount) { out(" asl $ESTACK_LO_PLUS1_HEX,x | rol $ESTACK_HI_PLUS1_HEX,x") } - return - } - "*" -> { - val value = expr.right.constValue(program) - if(value!=null) { - if(rightDt in IntegerDatatypes) { - val amount = value.number.toInt() - if(amount in powerOfTwos) - printWarning("${expr.right.position} multiplication by power of 2 should have been optimized into a left shift instruction: $amount") - when(rightDt) { - DataType.UBYTE -> { - if(amount in optimizedByteMultiplications) { - translateExpression(expr.left) - out(" jsr math.mul_byte_$amount") - return - } - } - DataType.BYTE -> { - if(amount in optimizedByteMultiplications) { - translateExpression(expr.left) - out(" jsr math.mul_byte_$amount") - return - } - if(amount.absoluteValue in optimizedByteMultiplications) { - translateExpression(expr.left) - out(" jsr prog8_lib.neg_b | jsr math.mul_byte_${amount.absoluteValue}") - return - } - } - DataType.UWORD -> { - if(amount in optimizedWordMultiplications) { - translateExpression(expr.left) - out(" jsr math.mul_word_$amount") - return - } - } - DataType.WORD -> { - if(amount in optimizedWordMultiplications) { - translateExpression(expr.left) - out(" jsr math.mul_word_$amount") - return - } - if(amount.absoluteValue in optimizedWordMultiplications) { - translateExpression(expr.left) - out(" jsr prog8_lib.neg_w | jsr math.mul_word_${amount.absoluteValue}") - return - } - } - else -> {} - } - } - } - } - } + fun assignFromRegister(target: AssignTarget, register: Register) = + assignmentAsmGen.assignFromRegister(target, register) - // the general, non-optimized cases - translateExpression(expr.left) - translateExpression(expr.right) - if(leftDt!=rightDt) - throw AssemblyError("binary operator ${expr.operator} left/right dt not identical") // is this strictly required always? - when (leftDt) { - in ByteDatatypes -> translateBinaryOperatorBytes(expr.operator, leftDt) - in WordDatatypes -> translateBinaryOperatorWords(expr.operator, leftDt) - DataType.FLOAT -> translateBinaryOperatorFloats(expr.operator) - else -> throw AssemblyError("non-numerical datatype") - } - } - - private fun translateExpression(expr: PrefixExpression) { - translateExpression(expr.expression) - val type = expr.inferType(program).typeOrElse(DataType.STRUCT) - when(expr.operator) { - "+" -> {} - "-" -> { - when(type) { - in ByteDatatypes -> out(" jsr prog8_lib.neg_b") - in WordDatatypes -> out(" jsr prog8_lib.neg_w") - DataType.FLOAT -> out(" jsr c64flt.neg_f") - else -> throw AssemblyError("weird type") - } - } - "~" -> { - when(type) { - in ByteDatatypes -> - out(""" - lda $ESTACK_LO_PLUS1_HEX,x - eor #255 - sta $ESTACK_LO_PLUS1_HEX,x - """) - in WordDatatypes -> out(" jsr prog8_lib.inv_word") - else -> throw AssemblyError("weird type") - } - } - "not" -> { - when(type) { - in ByteDatatypes -> out(" jsr prog8_lib.not_byte") - in WordDatatypes -> out(" jsr prog8_lib.not_word") - else -> throw AssemblyError("weird type") - } - } - else -> throw AssemblyError("invalid prefix operator ${expr.operator}") - } - } - - private fun translateBinaryOperatorBytes(operator: String, types: DataType) { - when(operator) { - "**" -> throw AssemblyError("** operator requires floats") - "*" -> out(" jsr prog8_lib.mul_byte") // the optimized routines should have been checked earlier - "/" -> out(if(types==DataType.UBYTE) " jsr prog8_lib.idiv_ub" else " jsr prog8_lib.idiv_b") - "%" -> { - if(types==DataType.BYTE) - throw AssemblyError("remainder of signed integers is not properly defined/implemented, use unsigned instead") - out(" jsr prog8_lib.remainder_ub") - } - "+" -> out(""" - lda $ESTACK_LO_PLUS2_HEX,x - clc - adc $ESTACK_LO_PLUS1_HEX,x - inx - sta $ESTACK_LO_PLUS1_HEX,x - """) - "-" -> out(""" - lda $ESTACK_LO_PLUS2_HEX,x - sec - sbc $ESTACK_LO_PLUS1_HEX,x - inx - sta $ESTACK_LO_PLUS1_HEX,x - """) - "<<", ">>" -> throw AssemblyError("bit-shifts not via stack") - "<" -> out(if(types==DataType.UBYTE) " jsr prog8_lib.less_ub" else " jsr prog8_lib.less_b") - ">" -> out(if(types==DataType.UBYTE) " jsr prog8_lib.greater_ub" else " jsr prog8_lib.greater_b") - "<=" -> out(if(types==DataType.UBYTE) " jsr prog8_lib.lesseq_ub" else " jsr prog8_lib.lesseq_b") - ">=" -> out(if(types==DataType.UBYTE) " jsr prog8_lib.greatereq_ub" else " jsr prog8_lib.greatereq_b") - "==" -> out(" jsr prog8_lib.equal_b") - "!=" -> out(" jsr prog8_lib.notequal_b") - "&" -> out(" jsr prog8_lib.bitand_b") - "^" -> out(" jsr prog8_lib.bitxor_b") - "|" -> out(" jsr prog8_lib.bitor_b") - "and" -> out(" jsr prog8_lib.and_b") - "or" -> out(" jsr prog8_lib.or_b") - "xor" -> out(" jsr prog8_lib.xor_b") - else -> throw AssemblyError("invalid operator $operator") - } - } - - private fun translateBinaryOperatorWords(operator: String, types: DataType) { - when(operator) { - "**" -> throw AssemblyError("** operator requires floats") - "*" -> out(" jsr prog8_lib.mul_word") - "/" -> out(if(types==DataType.UWORD) " jsr prog8_lib.idiv_uw" else " jsr prog8_lib.idiv_w") - "%" -> { - if(types==DataType.WORD) - throw AssemblyError("remainder of signed integers is not properly defined/implemented, use unsigned instead") - out(" jsr prog8_lib.remainder_uw") - } - "+" -> out(" jsr prog8_lib.add_w") - "-" -> out(" jsr prog8_lib.sub_w") - "<<" -> throw AssemblyError("<< should not operate via stack") - ">>" -> throw AssemblyError(">> should not operate via stack") - "<" -> out(if(types==DataType.UWORD) " jsr prog8_lib.less_uw" else " jsr prog8_lib.less_w") - ">" -> out(if(types==DataType.UWORD) " jsr prog8_lib.greater_uw" else " jsr prog8_lib.greater_w") - "<=" -> out(if(types==DataType.UWORD) " jsr prog8_lib.lesseq_uw" else " jsr prog8_lib.lesseq_w") - ">=" -> out(if(types==DataType.UWORD) " jsr prog8_lib.greatereq_uw" else " jsr prog8_lib.greatereq_w") - "==" -> out(" jsr prog8_lib.equal_w") - "!=" -> out(" jsr prog8_lib.notequal_w") - "&" -> out(" jsr prog8_lib.bitand_w") - "^" -> out(" jsr prog8_lib.bitxor_w") - "|" -> out(" jsr prog8_lib.bitor_w") - "and" -> out(" jsr prog8_lib.and_w") - "or" -> out(" jsr prog8_lib.or_w") - "xor" -> out(" jsr prog8_lib.xor_w") - else -> throw AssemblyError("invalid operator $operator") - } - } - - private fun translateBinaryOperatorFloats(operator: String) { - when(operator) { - "**" -> out(" jsr c64flt.pow_f") - "*" -> out(" jsr c64flt.mul_f") - "/" -> out(" jsr c64flt.div_f") - "+" -> out(" jsr c64flt.add_f") - "-" -> out(" jsr c64flt.sub_f") - "<" -> out(" jsr c64flt.less_f") - ">" -> out(" jsr c64flt.greater_f") - "<=" -> out(" jsr c64flt.lesseq_f") - ">=" -> out(" jsr c64flt.greatereq_f") - "==" -> out(" jsr c64flt.equal_f") - "!=" -> out(" jsr c64flt.notequal_f") - "%", "<<", ">>", "&", "^", "|", "and", "or", "xor" -> throw AssemblyError("requires integer datatype") - else -> throw AssemblyError("invalid operator $operator") - } - } - - internal fun assignFromEvalResult(target: AssignTarget) { - val targetIdent = target.identifier - when { - target.register!=null -> { - if(target.register==Register.X) - throw AssemblyError("can't pop into X register - use variable instead") - out(" inx | ld${target.register.name.toLowerCase()} $ESTACK_LO_HEX,x ") - } - targetIdent!=null -> { - val targetName = asmIdentifierName(targetIdent) - val targetDt = targetIdent.inferType(program).typeOrElse(DataType.STRUCT) - when(targetDt) { - DataType.UBYTE, DataType.BYTE -> { - out(" inx | lda $ESTACK_LO_HEX,x | sta $targetName") - } - DataType.UWORD, DataType.WORD -> { - out(""" - inx - lda $ESTACK_LO_HEX,x - sta $targetName - lda $ESTACK_HI_HEX,x - sta $targetName+1 - """) - } - DataType.FLOAT -> { - out(""" - lda #<$targetName - ldy #>$targetName - jsr c64flt.pop_float - """) - } - else -> throw AssemblyError("weird target variable type $targetDt") - } - } - target.memoryAddress!=null -> { - out(" inx | ldy $ESTACK_LO_HEX,x") - storeRegisterInMemoryAddress(Register.Y, target.memoryAddress) - } - target.arrayindexed!=null -> { - val arrayDt = target.arrayindexed!!.identifier.targetVarDecl(program.namespace)!!.datatype - val arrayVarName = asmIdentifierName(target.arrayindexed!!.identifier) - translateExpression(target.arrayindexed!!.arrayspec.index) - out(" inx | lda $ESTACK_LO_HEX,x") - popAndWriteArrayvalueWithIndexA(arrayDt, arrayVarName) - } - else -> throw AssemblyError("weird assignment target $target") - } - } - - private fun assignFromAddressOf(target: AssignTarget, name: IdentifierReference) { - val targetIdent = target.identifier - val targetArrayIdx = target.arrayindexed - val struct = name.memberOfStruct(program.namespace) - val sourceName = if(struct!=null) { - // take the address of the first struct member instead - val decl = name.targetVarDecl(program.namespace)!! - val firstStructMember = struct.nameOfFirstMember() - // find the flattened var that belongs to this first struct member - val firstVarName = listOf(decl.name, firstStructMember) - val firstVar = name.definingScope().lookup(firstVarName, name) as VarDecl - firstVar.name - } else { - fixNameSymbols(name.nameInSource.joinToString (".")) - } - - when { - targetIdent!=null -> { - val targetName = asmIdentifierName(targetIdent) - out(""" - lda #<$sourceName - ldy #>$sourceName - sta $targetName - sty $targetName+1 - """) - } - target.memoryAddress!=null -> { - TODO("assign address $sourceName to memory word $target") - } - targetArrayIdx!=null -> { - val index = targetArrayIdx.arrayspec.index - val targetName = asmIdentifierName(targetArrayIdx.identifier) - TODO("assign address $sourceName to array $targetName [ $index ]") - } - else -> TODO("assign address $sourceName to $target") - } - } - - private fun assignFromWordVariable(target: AssignTarget, variable: IdentifierReference) { - val sourceName = asmIdentifierName(variable) - val targetIdent = target.identifier - val targetArrayIdx = target.arrayindexed - when { - targetIdent!=null -> { - val targetName = asmIdentifierName(targetIdent) - out(""" - lda $sourceName - ldy $sourceName+1 - sta $targetName - sty $targetName+1 - """) - } - target.memoryAddress!=null -> { - TODO("assign wordvar $sourceName to memory ${target.memoryAddress}") - } - targetArrayIdx!=null -> { - val index = targetArrayIdx.arrayspec.index - val targetName = asmIdentifierName(targetArrayIdx.identifier) - out(" lda $sourceName | sta $ESTACK_LO_HEX,x | lda $sourceName+1 | sta $ESTACK_HI_HEX,x | dex") - translateExpression(index) - out(" inx | lda $ESTACK_LO_HEX,x") - val arrayDt = targetArrayIdx.identifier.inferType(program).typeOrElse(DataType.STRUCT) - popAndWriteArrayvalueWithIndexA(arrayDt, targetName) - } - else -> TODO("assign wordvar to $target") - } - } - - private fun assignFromFloatVariable(target: AssignTarget, variable: IdentifierReference) { - val sourceName = asmIdentifierName(variable) - val targetIdent = target.identifier - val targetArrayIdx = target.arrayindexed - when { - targetIdent!=null -> { - val targetName = asmIdentifierName(targetIdent) - out(""" - lda $sourceName - sta $targetName - lda $sourceName+1 - sta $targetName+1 - lda $sourceName+2 - sta $targetName+2 - lda $sourceName+3 - sta $targetName+3 - lda $sourceName+4 - sta $targetName+4 - """) - } - targetArrayIdx!=null -> { - val index = targetArrayIdx.arrayspec.index - val targetName = asmIdentifierName(targetArrayIdx.identifier) - out(" lda #<$sourceName | ldy #>$sourceName | jsr c64flt.push_float") - translateExpression(index) - out(" lda #<$targetName | ldy #>$targetName | jsr c64flt.pop_float_to_indexed_var") - } - else -> TODO("assign floatvar to $target") - } - } - - private fun assignFromByteVariable(target: AssignTarget, variable: IdentifierReference) { - val sourceName = asmIdentifierName(variable) - val targetIdent = target.identifier - val targetArrayIdx = target.arrayindexed - when { - target.register!=null -> { - out(" ld${target.register.name.toLowerCase()} $sourceName") - } - targetIdent!=null -> { - val targetName = asmIdentifierName(targetIdent) - out(""" - lda $sourceName - sta $targetName - """) - } - targetArrayIdx!=null -> { - val index = targetArrayIdx.arrayspec.index - val targetName = asmIdentifierName(targetArrayIdx.identifier) - val arrayDt = targetArrayIdx.identifier.inferType(program).typeOrElse(DataType.STRUCT) - out(" lda $sourceName | sta $ESTACK_LO_HEX,x | dex") - translateExpression(index) - out(" inx | lda $ESTACK_LO_HEX,x") - popAndWriteArrayvalueWithIndexA(arrayDt, targetName) - } - target.memoryAddress != null -> { - val addressExpr = target.memoryAddress.addressExpression - val addressLv = addressExpr as? NumericLiteralValue - when { - addressLv != null -> out(" lda $sourceName | sta ${addressLv.number.toHex()}") - addressExpr is IdentifierReference -> { - val targetName = asmIdentifierName(addressExpr) - out(" lda $sourceName | sta $targetName") - } - else -> { - translateExpression(addressExpr) - out(""" - inx - lda $ESTACK_LO_HEX,x - ldy $ESTACK_HI_HEX,x - sta (+) +1 - sty (+) +2 - lda $sourceName -+ sta ${65535.toHex()} ; modified - """) - } - } - } - else -> TODO("assign bytevar to $target") - } - } - - private fun assignFromRegister(target: AssignTarget, register: Register) { - val targetIdent = target.identifier - val targetArrayIdx = target.arrayindexed - when { - targetIdent!=null -> { - val targetName = asmIdentifierName(targetIdent) - out(" st${register.name.toLowerCase()} $targetName") - } - target.register!=null -> { - when(register) { - Register.A -> when(target.register) { - Register.A -> {} - Register.X -> out(" tax") - Register.Y -> out(" tay") - } - Register.X -> when(target.register) { - Register.A -> out(" txa") - Register.X -> {} - Register.Y -> out(" txy") - } - Register.Y -> when(target.register) { - Register.A -> out(" tya") - Register.X -> out(" tyx") - Register.Y -> {} - } - } - } - target.memoryAddress!=null -> { - storeRegisterInMemoryAddress(register, target.memoryAddress) - } - targetArrayIdx!=null -> { - val index = targetArrayIdx.arrayspec.index - val targetName = asmIdentifierName(targetArrayIdx.identifier) - when (index) { - is NumericLiteralValue -> { - val memindex = index.number.toInt() - when(register) { - Register.A -> out(" sta $targetName+$memindex") - Register.X -> out(" stx $targetName+$memindex") - Register.Y -> out(" sty $targetName+$memindex") - } - } - is RegisterExpr -> { - when(register) { - Register.A -> out(" sta ${C64Zeropage.SCRATCH_B1}") - Register.X -> out(" stx ${C64Zeropage.SCRATCH_B1}") - Register.Y -> out(" sty ${C64Zeropage.SCRATCH_B1}") - } - when(index.register) { - Register.A -> {} - Register.X -> out(" txa") - Register.Y -> out(" tya") - } - out(""" - tay - lda ${C64Zeropage.SCRATCH_B1} - sta $targetName,y - """) - } - is IdentifierReference -> { - when(register) { - Register.A -> out(" sta ${C64Zeropage.SCRATCH_B1}") - Register.X -> out(" stx ${C64Zeropage.SCRATCH_B1}") - Register.Y -> out(" sty ${C64Zeropage.SCRATCH_B1}") - } - out(""" - lda ${asmIdentifierName(index)} - tay - lda ${C64Zeropage.SCRATCH_B1} - sta $targetName,y - """) - } - else -> { - saveRegister(register) - translateExpression(index) - restoreRegister(register) - when(register) { - Register.A -> out(" sta ${C64Zeropage.SCRATCH_B1}") - Register.X -> out(" stx ${C64Zeropage.SCRATCH_B1}") - Register.Y -> out(" sty ${C64Zeropage.SCRATCH_B1}") - } - out(""" - inx - lda $ESTACK_LO_HEX,x - tay - lda ${C64Zeropage.SCRATCH_B1} - sta $targetName,y - """) - } - } - } - else -> TODO("assign register $register to $target") - } - } - - private fun storeRegisterInMemoryAddress(register: Register, memoryAddress: DirectMemoryWrite) { - val addressExpr = memoryAddress.addressExpression - val addressLv = addressExpr as? NumericLiteralValue - val registerName = register.name.toLowerCase() - when { - addressLv != null -> out(" st$registerName ${addressLv.number.toHex()}") - addressExpr is IdentifierReference -> { - val targetName = asmIdentifierName(addressExpr) - when(register) { - Register.A -> out(""" - ldy $targetName - sty ${C64Zeropage.SCRATCH_W1} - ldy $targetName+1 - sty ${C64Zeropage.SCRATCH_W1+1} - ldy #0 - sta (${C64Zeropage.SCRATCH_W1}),y - """) - Register.X -> out(""" - txa - ldy $targetName - sty ${C64Zeropage.SCRATCH_W1} - ldy $targetName+1 - sty ${C64Zeropage.SCRATCH_W1+1} - ldy #0 - sta (${C64Zeropage.SCRATCH_W1}),y - """) - Register.Y -> out(""" - tya - ldy $targetName - sty ${C64Zeropage.SCRATCH_W1} - ldy $targetName+1 - sty ${C64Zeropage.SCRATCH_W1+1} - ldy #0 - sta (${C64Zeropage.SCRATCH_W1}),y - """) - } - } - else -> { - saveRegister(register) - translateExpression(addressExpr) - restoreRegister(register) - when (register) { - Register.A -> out(" tay") - Register.X -> throw AssemblyError("can't use X register here") - Register.Y -> {} - } - out(""" - inx - lda $ESTACK_LO_HEX,x - sta (+) +1 - lda $ESTACK_HI_HEX,x - sta (+) +2 -+ sty ${65535.toHex()} ; modified - """) - } - } - } - - private fun assignFromWordConstant(target: AssignTarget, word: Int) { - val targetIdent = target.identifier - val targetArrayIdx = target.arrayindexed - when { - targetIdent!=null -> { - val targetName = asmIdentifierName(targetIdent) - if(word ushr 8 == word and 255) { - // lsb=msb - out(""" - lda #${(word and 255).toHex()} - sta $targetName - sta $targetName+1 - """) - } else { - out(""" - lda #<${word.toHex()} - ldy #>${word.toHex()} - sta $targetName - sty $targetName+1 - """) - } - } - target.memoryAddress!=null -> { - TODO("assign word $word to memory ${target.memoryAddress}") - } - targetArrayIdx!=null -> { - val index = targetArrayIdx.arrayspec.index - val targetName = asmIdentifierName(targetArrayIdx.identifier) - // TODO optimize common cases - translateExpression(index) - out(""" - inx - lda $ESTACK_LO_HEX,x - asl a - tay - lda #<${word.toHex()} - sta $targetName,y - lda #>${word.toHex()} - sta $targetName+1,y - """) - } - else -> TODO("assign word $word to $target") - } - } - - private fun assignFromByteConstant(target: AssignTarget, byte: Short) { - val targetIdent = target.identifier - val targetArrayIdx = target.arrayindexed - when { - target.register!=null -> { - out(" ld${target.register.name.toLowerCase()} #${byte.toHex()}") - } - targetIdent!=null -> { - val targetName = asmIdentifierName(targetIdent) - out(" lda #${byte.toHex()} | sta $targetName ") - } - target.memoryAddress!=null -> { - out(" ldy #${byte.toHex()}") - storeRegisterInMemoryAddress(Register.Y, target.memoryAddress) - } - targetArrayIdx!=null -> { - val index = targetArrayIdx.arrayspec.index - val targetName = asmIdentifierName(targetArrayIdx.identifier) - // TODO optimize common cases - translateExpression(index) - out(""" - inx - ldy $ESTACK_LO_HEX,x - lda #${byte.toHex()} - sta $targetName,y - """) - } - else -> TODO("assign byte $byte to $target") - } - } - - private fun assignFromFloatConstant(target: AssignTarget, float: Double) { - val targetIdent = target.identifier - val targetArrayIdx = target.arrayindexed - if(float==0.0) { - // optimized case for float zero - when { - targetIdent != null -> { - val targetName = asmIdentifierName(targetIdent) - out(""" - lda #0 - sta $targetName - sta $targetName+1 - sta $targetName+2 - sta $targetName+3 - sta $targetName+4 - """) - } - targetArrayIdx!=null -> { - val index = targetArrayIdx.arrayspec.index - val targetName = asmIdentifierName(targetArrayIdx.identifier) - if(index is NumericLiteralValue) { - val indexValue = index.number.toInt() * MachineDefinition.Mflpt5.MemorySize - out(""" - lda #0 - sta $targetName+$indexValue - sta $targetName+$indexValue+1 - sta $targetName+$indexValue+2 - sta $targetName+$indexValue+3 - sta $targetName+$indexValue+4 - """) - } else { - translateExpression(index) - out(""" - inx - lda $ESTACK_LO_HEX,x - asl a - asl a - clc - adc $ESTACK_LO_HEX,x - tay - lda #0 - sta $targetName,y - sta $targetName+1,y - sta $targetName+2,y - sta $targetName+3,y - sta $targetName+4,y - """) // TODO use a subroutine for this - } - } - else -> TODO("assign float 0.0 to $target") - } - } else { - // non-zero value - val constFloat = getFloatConst(float) - when { - targetIdent != null -> { - val targetName = asmIdentifierName(targetIdent) - out(""" - lda $constFloat - sta $targetName - lda $constFloat+1 - sta $targetName+1 - lda $constFloat+2 - sta $targetName+2 - lda $constFloat+3 - sta $targetName+3 - lda $constFloat+4 - sta $targetName+4 - """) - } - targetArrayIdx!=null -> { - val index = targetArrayIdx.arrayspec.index - val arrayVarName = asmIdentifierName(targetArrayIdx.identifier) - if(index is NumericLiteralValue) { - val indexValue = index.number.toInt() * MachineDefinition.Mflpt5.MemorySize - out(""" - lda $constFloat - sta $arrayVarName+$indexValue - lda $constFloat+1 - sta $arrayVarName+$indexValue+1 - lda $constFloat+2 - sta $arrayVarName+$indexValue+2 - lda $constFloat+3 - sta $arrayVarName+$indexValue+3 - lda $constFloat+4 - sta $arrayVarName+$indexValue+4 - """) - } else { - translateArrayIndexIntoA(targetArrayIdx) - out(""" - sta ${C64Zeropage.SCRATCH_REG} - asl a - asl a - clc - adc ${C64Zeropage.SCRATCH_REG} - tay - lda $constFloat - sta $arrayVarName,y - lda $constFloat+1 - sta $arrayVarName+1,y - lda $constFloat+2 - sta $arrayVarName+2,y - lda $constFloat+3 - sta $arrayVarName+3,y - lda $constFloat+4 - sta $arrayVarName+4,y - """) // TODO use a subroutine for this - } - } - else -> TODO("assign float $float to $target") - } - } - } - - private fun assignFromMemoryByte(target: AssignTarget, address: Int?, identifier: IdentifierReference?) { - val targetIdent = target.identifier - val targetArrayIdx = target.arrayindexed - if(address!=null) { - when { - target.register!=null -> { - out(" ld${target.register.name.toLowerCase()} ${address.toHex()}") - } - targetIdent!=null -> { - val targetName = asmIdentifierName(targetIdent) - out(""" - lda ${address.toHex()} - sta $targetName - """) - } - target.memoryAddress!=null -> { - out(" ldy ${address.toHex()}") - storeRegisterInMemoryAddress(Register.Y, target.memoryAddress) - } - targetArrayIdx!=null -> { - val index = targetArrayIdx.arrayspec.index - val targetName = asmIdentifierName(targetArrayIdx.identifier) - TODO("assign memory byte at $address to array $targetName [ $index ]") - } - else -> TODO("assign memory byte $target") - } - } - else if(identifier!=null) { - val sourceName = asmIdentifierName(identifier) - when { - target.register!=null -> { - out(""" - ldy #0 - lda ($sourceName),y - """) - when(target.register){ - Register.A -> {} - Register.X -> out(" tax") - Register.Y -> out(" tay") - } - } - targetIdent!=null -> { - val targetName = asmIdentifierName(targetIdent) - out(""" - ldy #0 - lda ($sourceName),y - sta $targetName - """) - } - target.memoryAddress!=null -> { - out(" ldy $sourceName") - storeRegisterInMemoryAddress(Register.Y, target.memoryAddress) - } - targetArrayIdx!=null -> { - val index = targetArrayIdx.arrayspec.index - val targetName = asmIdentifierName(targetArrayIdx.identifier) - TODO("assign memory byte $sourceName to array $targetName [ $index ]") - } - else -> TODO("assign memory byte $target") - } - } - } + fun assignFromMemoryByte(target: AssignTarget, address: Int?, identifier: IdentifierReference?) = + assignmentAsmGen.assignFromMemoryByte(target, address, identifier) } diff --git a/compiler/src/prog8/compiler/target/c64/codegen/AssignmentAsmGen.kt b/compiler/src/prog8/compiler/target/c64/codegen/AssignmentAsmGen.kt new file mode 100644 index 000000000..cf8f6eda6 --- /dev/null +++ b/compiler/src/prog8/compiler/target/c64/codegen/AssignmentAsmGen.kt @@ -0,0 +1,745 @@ +package prog8.compiler.target.c64.codegen + +import prog8.ast.Program +import prog8.ast.base.* +import prog8.ast.expressions.* +import prog8.ast.statements.AssignTarget +import prog8.ast.statements.Assignment +import prog8.ast.statements.DirectMemoryWrite +import prog8.ast.statements.VarDecl +import prog8.compiler.target.c64.MachineDefinition +import prog8.compiler.toHex + +internal class AssignmentAsmGen(private val program: Program, private val asmgen: AsmGen) { + + internal fun translate(assign: Assignment) { + if(assign.aug_op!=null) + throw AssemblyError("aug-op assignments should have been transformed to normal ones") + + when(assign.value) { + is NumericLiteralValue -> { + val numVal = assign.value as NumericLiteralValue + when(numVal.type) { + DataType.UBYTE, DataType.BYTE -> assignFromByteConstant(assign.target, numVal.number.toShort()) + DataType.UWORD, DataType.WORD -> assignFromWordConstant(assign.target, numVal.number.toInt()) + DataType.FLOAT -> assignFromFloatConstant(assign.target, numVal.number.toDouble()) + else -> throw AssemblyError("weird numval type") + } + } + is RegisterExpr -> { + assignFromRegister(assign.target, (assign.value as RegisterExpr).register) + } + is IdentifierReference -> { + val type = assign.target.inferType(program, assign).typeOrElse(DataType.STRUCT) + when(type) { + DataType.UBYTE, DataType.BYTE -> assignFromByteVariable(assign.target, assign.value as IdentifierReference) + DataType.UWORD, DataType.WORD -> assignFromWordVariable(assign.target, assign.value as IdentifierReference) + DataType.FLOAT -> assignFromFloatVariable(assign.target, assign.value as IdentifierReference) + else -> throw AssemblyError("unsupported assignment target type $type") + } + } + is AddressOf -> { + val identifier = (assign.value as AddressOf).identifier + assignFromAddressOf(assign.target, identifier) + } + is DirectMemoryRead -> { + val read = (assign.value as DirectMemoryRead) + when(read.addressExpression) { + is NumericLiteralValue -> { + val address = (read.addressExpression as NumericLiteralValue).number.toInt() + assignFromMemoryByte(assign.target, address, null) + } + is IdentifierReference -> { + assignFromMemoryByte(assign.target, null, read.addressExpression as IdentifierReference) + } + else -> { + asmgen.translateExpression(read.addressExpression) + TODO("read memory byte from result and put that in ${assign.target}") + } + } + } + is PrefixExpression -> { + // TODO optimize common cases + asmgen.translateExpression(assign.value as PrefixExpression) + assignFromEvalResult(assign.target) + } + is BinaryExpression -> { + // TODO optimize common cases + asmgen.translateExpression(assign.value as BinaryExpression) + assignFromEvalResult(assign.target) + } + is ArrayIndexedExpression -> { + // TODO optimize common cases + val arrayExpr = assign.value as ArrayIndexedExpression + val arrayDt = arrayExpr.identifier.targetVarDecl(program.namespace)!!.datatype + val index = arrayExpr.arrayspec.index + if(index is NumericLiteralValue) { + // constant array index value + val arrayVarName = asmgen.asmIdentifierName(arrayExpr.identifier) + val indexValue = index.number.toInt() * ArrayElementTypes.getValue(arrayDt).memorySize() + when (arrayDt) { + DataType.STR, DataType.STR_S, DataType.ARRAY_UB, DataType.ARRAY_B -> + asmgen.out(" lda $arrayVarName+$indexValue | sta ${MachineDefinition.ESTACK_LO_HEX},x | dex") + DataType.ARRAY_UW, DataType.ARRAY_W -> + asmgen.out(" lda $arrayVarName+$indexValue | sta ${MachineDefinition.ESTACK_LO_HEX},x | lda $arrayVarName+$indexValue+1 | sta ${MachineDefinition.ESTACK_HI_HEX},x | dex") + DataType.ARRAY_F -> + asmgen.out(" lda #<$arrayVarName+$indexValue | ldy #>$arrayVarName+$indexValue | jsr c64flt.push_float") + else -> + throw AssemblyError("weird array type") + } + } else { + asmgen.translateArrayIndexIntoA(arrayExpr) + asmgen.readAndPushArrayvalueWithIndexA(arrayDt, arrayExpr.identifier) + } + assignFromEvalResult(assign.target) + } + is TypecastExpression -> { + val cast = assign.value as TypecastExpression + val sourceType = cast.expression.inferType(program) + val targetType = assign.target.inferType(program, assign) + if(sourceType.isKnown && targetType.isKnown && + (sourceType.typeOrElse(DataType.STRUCT) in ByteDatatypes && targetType.typeOrElse(DataType.STRUCT) in ByteDatatypes) || + (sourceType.typeOrElse(DataType.STRUCT) in WordDatatypes && targetType.typeOrElse(DataType.STRUCT) in WordDatatypes)) { + // no need for a type cast + assign.value = cast.expression + translate(assign) + } else { + asmgen.translateExpression(assign.value as TypecastExpression) + assignFromEvalResult(assign.target) + } + } + is FunctionCall -> { + asmgen.translateExpression(assign.value as FunctionCall) + assignFromEvalResult(assign.target) + } + is ArrayLiteralValue, is StringLiteralValue -> TODO("string/array/struct assignment?") + is StructLiteralValue -> throw AssemblyError("struct literal value assignment should have been flattened") + is RangeExpr -> throw AssemblyError("range expression should have been changed into array values") + } + } + + internal fun assignFromEvalResult(target: AssignTarget) { + val targetIdent = target.identifier + when { + target.register!=null -> { + if(target.register== Register.X) + throw AssemblyError("can't pop into X register - use variable instead") + asmgen.out(" inx | ld${target.register.name.toLowerCase()} ${MachineDefinition.ESTACK_LO_HEX},x ") + } + targetIdent!=null -> { + val targetName = asmgen.asmIdentifierName(targetIdent) + val targetDt = targetIdent.inferType(program).typeOrElse(DataType.STRUCT) + when(targetDt) { + DataType.UBYTE, DataType.BYTE -> { + asmgen.out(" inx | lda ${MachineDefinition.ESTACK_LO_HEX},x | sta $targetName") + } + DataType.UWORD, DataType.WORD -> { + asmgen.out(""" + inx + lda ${MachineDefinition.ESTACK_LO_HEX},x + sta $targetName + lda ${MachineDefinition.ESTACK_HI_HEX},x + sta $targetName+1 + """) + } + DataType.FLOAT -> { + asmgen.out(""" + lda #<$targetName + ldy #>$targetName + jsr c64flt.pop_float + """) + } + else -> throw AssemblyError("weird target variable type $targetDt") + } + } + target.memoryAddress!=null -> { + asmgen.out(" inx | ldy ${MachineDefinition.ESTACK_LO_HEX},x") + storeRegisterInMemoryAddress(Register.Y, target.memoryAddress) + } + target.arrayindexed!=null -> { + val arrayDt = target.arrayindexed!!.identifier.targetVarDecl(program.namespace)!!.datatype + val arrayVarName = asmgen.asmIdentifierName(target.arrayindexed!!.identifier) + asmgen.translateExpression(target.arrayindexed!!.arrayspec.index) + asmgen.out(" inx | lda ${MachineDefinition.ESTACK_LO_HEX},x") + popAndWriteArrayvalueWithIndexA(arrayDt, arrayVarName) + } + else -> throw AssemblyError("weird assignment target $target") + } + } + + internal fun assignFromAddressOf(target: AssignTarget, name: IdentifierReference) { + val targetIdent = target.identifier + val targetArrayIdx = target.arrayindexed + val struct = name.memberOfStruct(program.namespace) + val sourceName = if(struct!=null) { + // take the address of the first struct member instead + val decl = name.targetVarDecl(program.namespace)!! + val firstStructMember = struct.nameOfFirstMember() + // find the flattened var that belongs to this first struct member + val firstVarName = listOf(decl.name, firstStructMember) + val firstVar = name.definingScope().lookup(firstVarName, name) as VarDecl + firstVar.name + } else { + asmgen.fixNameSymbols(name.nameInSource.joinToString (".")) + } + + when { + targetIdent!=null -> { + val targetName = asmgen.asmIdentifierName(targetIdent) + asmgen.out(""" + lda #<$sourceName + ldy #>$sourceName + sta $targetName + sty $targetName+1 + """) + } + target.memoryAddress!=null -> { + TODO("assign address $sourceName to memory word $target") + } + targetArrayIdx!=null -> { + val index = targetArrayIdx.arrayspec.index + val targetName = asmgen.asmIdentifierName(targetArrayIdx.identifier) + TODO("assign address $sourceName to array $targetName [ $index ]") + } + else -> TODO("assign address $sourceName to $target") + } + } + + internal fun assignFromWordVariable(target: AssignTarget, variable: IdentifierReference) { + val sourceName = asmgen.asmIdentifierName(variable) + val targetIdent = target.identifier + val targetArrayIdx = target.arrayindexed + when { + targetIdent!=null -> { + val targetName = asmgen.asmIdentifierName(targetIdent) + asmgen.out(""" + lda $sourceName + ldy $sourceName+1 + sta $targetName + sty $targetName+1 + """) + } + target.memoryAddress!=null -> { + TODO("assign wordvar $sourceName to memory ${target.memoryAddress}") + } + targetArrayIdx!=null -> { + val index = targetArrayIdx.arrayspec.index + val targetName = asmgen.asmIdentifierName(targetArrayIdx.identifier) + asmgen.out(" lda $sourceName | sta ${MachineDefinition.ESTACK_LO_HEX},x | lda $sourceName+1 | sta ${MachineDefinition.ESTACK_HI_HEX},x | dex") + asmgen.translateExpression(index) + asmgen.out(" inx | lda ${MachineDefinition.ESTACK_LO_HEX},x") + val arrayDt = targetArrayIdx.identifier.inferType(program).typeOrElse(DataType.STRUCT) + popAndWriteArrayvalueWithIndexA(arrayDt, targetName) + } + else -> TODO("assign wordvar to $target") + } + } + + internal fun assignFromFloatVariable(target: AssignTarget, variable: IdentifierReference) { + val sourceName = asmgen.asmIdentifierName(variable) + val targetIdent = target.identifier + val targetArrayIdx = target.arrayindexed + when { + targetIdent!=null -> { + val targetName = asmgen.asmIdentifierName(targetIdent) + asmgen.out(""" + lda $sourceName + sta $targetName + lda $sourceName+1 + sta $targetName+1 + lda $sourceName+2 + sta $targetName+2 + lda $sourceName+3 + sta $targetName+3 + lda $sourceName+4 + sta $targetName+4 + """) + } + targetArrayIdx!=null -> { + val index = targetArrayIdx.arrayspec.index + val targetName = asmgen.asmIdentifierName(targetArrayIdx.identifier) + asmgen.out(" lda #<$sourceName | ldy #>$sourceName | jsr c64flt.push_float") + asmgen.translateExpression(index) + asmgen.out(" lda #<$targetName | ldy #>$targetName | jsr c64flt.pop_float_to_indexed_var") + } + else -> TODO("assign floatvar to $target") + } + } + + internal fun assignFromByteVariable(target: AssignTarget, variable: IdentifierReference) { + val sourceName = asmgen.asmIdentifierName(variable) + val targetIdent = target.identifier + val targetArrayIdx = target.arrayindexed + when { + target.register!=null -> { + asmgen.out(" ld${target.register.name.toLowerCase()} $sourceName") + } + targetIdent!=null -> { + val targetName = asmgen.asmIdentifierName(targetIdent) + asmgen.out(""" + lda $sourceName + sta $targetName + """) + } + targetArrayIdx!=null -> { + val index = targetArrayIdx.arrayspec.index + val targetName = asmgen.asmIdentifierName(targetArrayIdx.identifier) + val arrayDt = targetArrayIdx.identifier.inferType(program).typeOrElse(DataType.STRUCT) + asmgen.out(" lda $sourceName | sta ${MachineDefinition.ESTACK_LO_HEX},x | dex") + asmgen.translateExpression(index) + asmgen.out(" inx | lda ${MachineDefinition.ESTACK_LO_HEX},x") + popAndWriteArrayvalueWithIndexA(arrayDt, targetName) + } + target.memoryAddress != null -> { + val addressExpr = target.memoryAddress.addressExpression + val addressLv = addressExpr as? NumericLiteralValue + when { + addressLv != null -> asmgen.out(" lda $sourceName | sta ${addressLv.number.toHex()}") + addressExpr is IdentifierReference -> { + val targetName = asmgen.asmIdentifierName(addressExpr) + asmgen.out(" lda $sourceName | sta $targetName") + } + else -> { + asmgen.translateExpression(addressExpr) + asmgen.out(""" + inx + lda ${MachineDefinition.ESTACK_LO_HEX},x + ldy ${MachineDefinition.ESTACK_HI_HEX},x + sta (+) +1 + sty (+) +2 + lda $sourceName ++ sta ${65535.toHex()} ; modified + """) + } + } + } + else -> TODO("assign bytevar to $target") + } + } + + internal fun assignFromRegister(target: AssignTarget, register: Register) { + val targetIdent = target.identifier + val targetArrayIdx = target.arrayindexed + when { + targetIdent!=null -> { + val targetName = asmgen.asmIdentifierName(targetIdent) + asmgen.out(" st${register.name.toLowerCase()} $targetName") + } + target.register!=null -> { + when(register) { + Register.A -> when(target.register) { + Register.A -> {} + Register.X -> asmgen.out(" tax") + Register.Y -> asmgen.out(" tay") + } + Register.X -> when(target.register) { + Register.A -> asmgen.out(" txa") + Register.X -> {} + Register.Y -> asmgen.out(" txy") + } + Register.Y -> when(target.register) { + Register.A -> asmgen.out(" tya") + Register.X -> asmgen.out(" tyx") + Register.Y -> {} + } + } + } + target.memoryAddress!=null -> { + storeRegisterInMemoryAddress(register, target.memoryAddress) + } + targetArrayIdx!=null -> { + val index = targetArrayIdx.arrayspec.index + val targetName = asmgen.asmIdentifierName(targetArrayIdx.identifier) + when (index) { + is NumericLiteralValue -> { + val memindex = index.number.toInt() + when(register) { + Register.A -> asmgen.out(" sta $targetName+$memindex") + Register.X -> asmgen.out(" stx $targetName+$memindex") + Register.Y -> asmgen.out(" sty $targetName+$memindex") + } + } + is RegisterExpr -> { + when(register) { + Register.A -> asmgen.out(" sta ${MachineDefinition.C64Zeropage.SCRATCH_B1}") + Register.X -> asmgen.out(" stx ${MachineDefinition.C64Zeropage.SCRATCH_B1}") + Register.Y -> asmgen.out(" sty ${MachineDefinition.C64Zeropage.SCRATCH_B1}") + } + when(index.register) { + Register.A -> {} + Register.X -> asmgen.out(" txa") + Register.Y -> asmgen.out(" tya") + } + asmgen.out(""" + tay + lda ${MachineDefinition.C64Zeropage.SCRATCH_B1} + sta $targetName,y + """) + } + is IdentifierReference -> { + when(register) { + Register.A -> asmgen.out(" sta ${MachineDefinition.C64Zeropage.SCRATCH_B1}") + Register.X -> asmgen.out(" stx ${MachineDefinition.C64Zeropage.SCRATCH_B1}") + Register.Y -> asmgen.out(" sty ${MachineDefinition.C64Zeropage.SCRATCH_B1}") + } + asmgen.out(""" + lda ${asmgen.asmIdentifierName(index)} + tay + lda ${MachineDefinition.C64Zeropage.SCRATCH_B1} + sta $targetName,y + """) + } + else -> { + asmgen.saveRegister(register) + asmgen.translateExpression(index) + asmgen.restoreRegister(register) + when(register) { + Register.A -> asmgen.out(" sta ${MachineDefinition.C64Zeropage.SCRATCH_B1}") + Register.X -> asmgen.out(" stx ${MachineDefinition.C64Zeropage.SCRATCH_B1}") + Register.Y -> asmgen.out(" sty ${MachineDefinition.C64Zeropage.SCRATCH_B1}") + } + asmgen.out(""" + inx + lda ${MachineDefinition.ESTACK_LO_HEX},x + tay + lda ${MachineDefinition.C64Zeropage.SCRATCH_B1} + sta $targetName,y + """) + } + } + } + else -> TODO("assign register $register to $target") + } + } + + private fun storeRegisterInMemoryAddress(register: Register, memoryAddress: DirectMemoryWrite) { + val addressExpr = memoryAddress.addressExpression + val addressLv = addressExpr as? NumericLiteralValue + val registerName = register.name.toLowerCase() + when { + addressLv != null -> asmgen.out(" st$registerName ${addressLv.number.toHex()}") + addressExpr is IdentifierReference -> { + val targetName = asmgen.asmIdentifierName(addressExpr) + when(register) { + Register.A -> asmgen.out(""" + ldy $targetName + sty ${MachineDefinition.C64Zeropage.SCRATCH_W1} + ldy $targetName+1 + sty ${MachineDefinition.C64Zeropage.SCRATCH_W1+1} + ldy #0 + sta (${MachineDefinition.C64Zeropage.SCRATCH_W1}),y + """) + Register.X -> asmgen.out(""" + txa + ldy $targetName + sty ${MachineDefinition.C64Zeropage.SCRATCH_W1} + ldy $targetName+1 + sty ${MachineDefinition.C64Zeropage.SCRATCH_W1+1} + ldy #0 + sta (${MachineDefinition.C64Zeropage.SCRATCH_W1}),y + """) + Register.Y -> asmgen.out(""" + tya + ldy $targetName + sty ${MachineDefinition.C64Zeropage.SCRATCH_W1} + ldy $targetName+1 + sty ${MachineDefinition.C64Zeropage.SCRATCH_W1+1} + ldy #0 + sta (${MachineDefinition.C64Zeropage.SCRATCH_W1}),y + """) + } + } + else -> { + asmgen.saveRegister(register) + asmgen.translateExpression(addressExpr) + asmgen.restoreRegister(register) + when (register) { + Register.A -> asmgen.out(" tay") + Register.X -> throw AssemblyError("can't use X register here") + Register.Y -> {} + } + asmgen.out(""" + inx + lda ${MachineDefinition.ESTACK_LO_HEX},x + sta (+) +1 + lda ${MachineDefinition.ESTACK_HI_HEX},x + sta (+) +2 ++ sty ${65535.toHex()} ; modified + """) + } + } + } + + internal fun assignFromWordConstant(target: AssignTarget, word: Int) { + val targetIdent = target.identifier + val targetArrayIdx = target.arrayindexed + when { + targetIdent!=null -> { + val targetName = asmgen.asmIdentifierName(targetIdent) + if(word ushr 8 == word and 255) { + // lsb=msb + asmgen.out(""" + lda #${(word and 255).toHex()} + sta $targetName + sta $targetName+1 + """) + } else { + asmgen.out(""" + lda #<${word.toHex()} + ldy #>${word.toHex()} + sta $targetName + sty $targetName+1 + """) + } + } + target.memoryAddress!=null -> { + TODO("assign word $word to memory ${target.memoryAddress}") + } + targetArrayIdx!=null -> { + val index = targetArrayIdx.arrayspec.index + val targetName = asmgen.asmIdentifierName(targetArrayIdx.identifier) + // TODO optimize common cases + asmgen.translateExpression(index) + asmgen.out(""" + inx + lda ${MachineDefinition.ESTACK_LO_HEX},x + asl a + tay + lda #<${word.toHex()} + sta $targetName,y + lda #>${word.toHex()} + sta $targetName+1,y + """) + } + else -> TODO("assign word $word to $target") + } + } + + internal fun assignFromByteConstant(target: AssignTarget, byte: Short) { + val targetIdent = target.identifier + val targetArrayIdx = target.arrayindexed + when { + target.register!=null -> { + asmgen.out(" ld${target.register.name.toLowerCase()} #${byte.toHex()}") + } + targetIdent!=null -> { + val targetName = asmgen.asmIdentifierName(targetIdent) + asmgen.out(" lda #${byte.toHex()} | sta $targetName ") + } + target.memoryAddress!=null -> { + asmgen.out(" ldy #${byte.toHex()}") + storeRegisterInMemoryAddress(Register.Y, target.memoryAddress) + } + targetArrayIdx!=null -> { + val index = targetArrayIdx.arrayspec.index + val targetName = asmgen.asmIdentifierName(targetArrayIdx.identifier) + // TODO optimize common cases + asmgen.translateExpression(index) + asmgen.out(""" + inx + ldy ${MachineDefinition.ESTACK_LO_HEX},x + lda #${byte.toHex()} + sta $targetName,y + """) + } + else -> TODO("assign byte $byte to $target") + } + } + + internal fun assignFromFloatConstant(target: AssignTarget, float: Double) { + val targetIdent = target.identifier + val targetArrayIdx = target.arrayindexed + if(float==0.0) { + // optimized case for float zero + when { + targetIdent != null -> { + val targetName = asmgen.asmIdentifierName(targetIdent) + asmgen.out(""" + lda #0 + sta $targetName + sta $targetName+1 + sta $targetName+2 + sta $targetName+3 + sta $targetName+4 + """) + } + targetArrayIdx!=null -> { + val index = targetArrayIdx.arrayspec.index + val targetName = asmgen.asmIdentifierName(targetArrayIdx.identifier) + if(index is NumericLiteralValue) { + val indexValue = index.number.toInt() * MachineDefinition.Mflpt5.MemorySize + asmgen.out(""" + lda #0 + sta $targetName+$indexValue + sta $targetName+$indexValue+1 + sta $targetName+$indexValue+2 + sta $targetName+$indexValue+3 + sta $targetName+$indexValue+4 + """) + } else { + asmgen.translateExpression(index) + asmgen.out(""" + inx + lda ${MachineDefinition.ESTACK_LO_HEX},x + asl a + asl a + clc + adc ${MachineDefinition.ESTACK_LO_HEX},x + tay + lda #0 + sta $targetName,y + sta $targetName+1,y + sta $targetName+2,y + sta $targetName+3,y + sta $targetName+4,y + """) // TODO use a subroutine for this + } + } + else -> TODO("assign float 0.0 to $target") + } + } else { + // non-zero value + val constFloat = asmgen.getFloatConst(float) + when { + targetIdent != null -> { + val targetName = asmgen.asmIdentifierName(targetIdent) + asmgen.out(""" + lda $constFloat + sta $targetName + lda $constFloat+1 + sta $targetName+1 + lda $constFloat+2 + sta $targetName+2 + lda $constFloat+3 + sta $targetName+3 + lda $constFloat+4 + sta $targetName+4 + """) + } + targetArrayIdx!=null -> { + val index = targetArrayIdx.arrayspec.index + val arrayVarName = asmgen.asmIdentifierName(targetArrayIdx.identifier) + if(index is NumericLiteralValue) { + val indexValue = index.number.toInt() * MachineDefinition.Mflpt5.MemorySize + asmgen.out(""" + lda $constFloat + sta $arrayVarName+$indexValue + lda $constFloat+1 + sta $arrayVarName+$indexValue+1 + lda $constFloat+2 + sta $arrayVarName+$indexValue+2 + lda $constFloat+3 + sta $arrayVarName+$indexValue+3 + lda $constFloat+4 + sta $arrayVarName+$indexValue+4 + """) + } else { + asmgen.translateArrayIndexIntoA(targetArrayIdx) + asmgen.out(""" + sta ${MachineDefinition.C64Zeropage.SCRATCH_REG} + asl a + asl a + clc + adc ${MachineDefinition.C64Zeropage.SCRATCH_REG} + tay + lda $constFloat + sta $arrayVarName,y + lda $constFloat+1 + sta $arrayVarName+1,y + lda $constFloat+2 + sta $arrayVarName+2,y + lda $constFloat+3 + sta $arrayVarName+3,y + lda $constFloat+4 + sta $arrayVarName+4,y + """) // TODO use a subroutine for this + } + } + else -> TODO("assign float $float to $target") + } + } + } + + internal fun assignFromMemoryByte(target: AssignTarget, address: Int?, identifier: IdentifierReference?) { + val targetIdent = target.identifier + val targetArrayIdx = target.arrayindexed + if(address!=null) { + when { + target.register!=null -> { + asmgen.out(" ld${target.register.name.toLowerCase()} ${address.toHex()}") + } + targetIdent!=null -> { + val targetName = asmgen.asmIdentifierName(targetIdent) + asmgen.out(""" + lda ${address.toHex()} + sta $targetName + """) + } + target.memoryAddress!=null -> { + asmgen.out(" ldy ${address.toHex()}") + storeRegisterInMemoryAddress(Register.Y, target.memoryAddress) + } + targetArrayIdx!=null -> { + val index = targetArrayIdx.arrayspec.index + val targetName = asmgen.asmIdentifierName(targetArrayIdx.identifier) + TODO("assign memory byte at $address to array $targetName [ $index ]") + } + else -> TODO("assign memory byte $target") + } + } + else if(identifier!=null) { + val sourceName = asmgen.asmIdentifierName(identifier) + when { + target.register!=null -> { + asmgen.out(""" + ldy #0 + lda ($sourceName),y + """) + when(target.register){ + Register.A -> {} + Register.X -> asmgen.out(" tax") + Register.Y -> asmgen.out(" tay") + } + } + targetIdent!=null -> { + val targetName = asmgen.asmIdentifierName(targetIdent) + asmgen.out(""" + ldy #0 + lda ($sourceName),y + sta $targetName + """) + } + target.memoryAddress!=null -> { + asmgen.out(" ldy $sourceName") + storeRegisterInMemoryAddress(Register.Y, target.memoryAddress) + } + targetArrayIdx!=null -> { + val index = targetArrayIdx.arrayspec.index + val targetName = asmgen.asmIdentifierName(targetArrayIdx.identifier) + TODO("assign memory byte $sourceName to array $targetName [ $index ]") + } + else -> TODO("assign memory byte $target") + } + } + } + + private fun popAndWriteArrayvalueWithIndexA(arrayDt: DataType, variablename: String) { + when (arrayDt) { + DataType.STR, DataType.STR_S, DataType.ARRAY_UB, DataType.ARRAY_B -> + asmgen.out(" tay | inx | lda ${MachineDefinition.ESTACK_LO_HEX},x | sta $variablename,y") + DataType.ARRAY_UW, DataType.ARRAY_W -> + asmgen.out(" asl a | tay | inx | lda ${MachineDefinition.ESTACK_LO_HEX},x | sta $variablename,y | lda ${MachineDefinition.ESTACK_HI_HEX},x | sta $variablename+1,y") + DataType.ARRAY_F -> + // index * 5 is done in the subroutine that's called + asmgen.out(""" + sta ${MachineDefinition.ESTACK_LO_HEX},x + dex + lda #<$variablename + ldy #>$variablename + jsr c64flt.pop_float_to_indexed_var + """) + else -> + throw AssemblyError("weird array type") + } + } +} diff --git a/compiler/src/prog8/compiler/target/c64/codegen/BuiltinFunctionsAsmGen.kt b/compiler/src/prog8/compiler/target/c64/codegen/BuiltinFunctionsAsmGen.kt index 5ba9da502..0925641a2 100644 --- a/compiler/src/prog8/compiler/target/c64/codegen/BuiltinFunctionsAsmGen.kt +++ b/compiler/src/prog8/compiler/target/c64/codegen/BuiltinFunctionsAsmGen.kt @@ -9,8 +9,6 @@ import prog8.ast.base.WordDatatypes import prog8.ast.expressions.* import prog8.ast.statements.AssignTarget import prog8.ast.statements.FunctionCallStatement -import prog8.compiler.CompilationOptions -import prog8.compiler.Zeropage import prog8.compiler.target.c64.MachineDefinition.ESTACK_HI_HEX import prog8.compiler.target.c64.MachineDefinition.ESTACK_HI_PLUS1_HEX import prog8.compiler.target.c64.MachineDefinition.ESTACK_LO_HEX @@ -18,10 +16,7 @@ import prog8.compiler.target.c64.MachineDefinition.ESTACK_LO_PLUS1_HEX import prog8.compiler.toHex import prog8.functions.FunctionSignature -internal class BuiltinFunctionsAsmGen(private val program: Program, - private val options: CompilationOptions, - private val zeropage: Zeropage, - private val asmgen: AsmGen) { +internal class BuiltinFunctionsAsmGen(private val program: Program, private val asmgen: AsmGen) { internal fun translateFunctioncallExpression(fcall: FunctionCall, func: FunctionSignature) { translateFunctioncall(fcall, func, false) @@ -33,21 +28,21 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private fun translateFunctioncall(fcall: IFunctionCall, func: FunctionSignature, discardResult: Boolean) { val functionName = fcall.target.nameInSource.last() - if(discardResult) { - if(func.pure) + if (discardResult) { + if (func.pure) return // can just ignore the whole function call altogether - else if(func.returntype!=null) + else if (func.returntype != null) throw AssemblyError("discarding result of non-pure function $fcall") } - when(functionName) { + when (functionName) { "msb" -> { val arg = fcall.arglist.single() - if(arg.inferType(program).typeOrElse(DataType.STRUCT) !in WordDatatypes) + if (arg.inferType(program).typeOrElse(DataType.STRUCT) !in WordDatatypes) throw AssemblyError("msb required word argument") - if(arg is NumericLiteralValue) + if (arg is NumericLiteralValue) throw AssemblyError("should have been const-folded") - if(arg is IdentifierReference) { + if (arg is IdentifierReference) { val sourceName = asmgen.asmIdentifierName(arg) asmgen.out(" lda $sourceName+1 | sta $ESTACK_LO_HEX,x | dex") } else { @@ -87,7 +82,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, "min", "max", "sum" -> { outputPushAddressAndLenghtOfArray(fcall.arglist[0]) val dt = fcall.arglist.single().inferType(program) - when(dt.typeOrElse(DataType.STRUCT)) { + when (dt.typeOrElse(DataType.STRUCT)) { DataType.ARRAY_UB, DataType.STR_S, DataType.STR -> asmgen.out(" jsr prog8_lib.func_${functionName}_ub") DataType.ARRAY_B -> asmgen.out(" jsr prog8_lib.func_${functionName}_b") DataType.ARRAY_UW -> asmgen.out(" jsr prog8_lib.func_${functionName}_uw") @@ -99,7 +94,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, "any", "all" -> { outputPushAddressAndLenghtOfArray(fcall.arglist[0]) val dt = fcall.arglist.single().inferType(program) - when(dt.typeOrElse(DataType.STRUCT)) { + when (dt.typeOrElse(DataType.STRUCT)) { DataType.ARRAY_B, DataType.ARRAY_UB, DataType.STR_S, DataType.STR -> asmgen.out(" jsr prog8_lib.func_${functionName}_b") DataType.ARRAY_UW, DataType.ARRAY_W -> asmgen.out(" jsr prog8_lib.func_${functionName}_w") DataType.ARRAY_F -> asmgen.out(" jsr c64flt.func_${functionName}_f") @@ -135,11 +130,11 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, // in-place val what = fcall.arglist.single() val dt = what.inferType(program) - when(dt.typeOrElse(DataType.STRUCT)) { + when (dt.typeOrElse(DataType.STRUCT)) { in ByteDatatypes -> { - when(what) { + when (what) { is RegisterExpr -> { - when(what.register) { + when (what.register) { Register.A -> asmgen.out(" asl a") Register.X -> asmgen.out(" txa | asl a | tax") Register.Y -> asmgen.out(" tya | asl a | tay") @@ -147,7 +142,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, } is IdentifierReference -> asmgen.out(" asl ${asmgen.asmIdentifierName(what)}") is DirectMemoryRead -> { - if(what.addressExpression is NumericLiteralValue) { + if (what.addressExpression is NumericLiteralValue) { asmgen.out(" asl ${(what.addressExpression as NumericLiteralValue).number.toHex()}") } else { TODO("lsl memory byte $what") @@ -160,7 +155,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, } } in WordDatatypes -> { - when(what) { + when (what) { is ArrayIndexedExpression -> TODO("lsl sbyte $what") is IdentifierReference -> { val variable = asmgen.asmIdentifierName(what) @@ -176,11 +171,11 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, // in-place val what = fcall.arglist.single() val dt = what.inferType(program) - when(dt.typeOrElse(DataType.STRUCT)) { + when (dt.typeOrElse(DataType.STRUCT)) { DataType.UBYTE -> { - when(what) { + when (what) { is RegisterExpr -> { - when(what.register) { + when (what.register) { Register.A -> asmgen.out(" lsr a") Register.X -> asmgen.out(" txa | lsr a | tax") Register.Y -> asmgen.out(" tya | lsr a | tay") @@ -188,7 +183,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, } is IdentifierReference -> asmgen.out(" lsr ${asmgen.asmIdentifierName(what)}") is DirectMemoryRead -> { - if(what.addressExpression is NumericLiteralValue) { + if (what.addressExpression is NumericLiteralValue) { asmgen.out(" lsr ${(what.addressExpression as NumericLiteralValue).number.toHex()}") } else { TODO("lsr memory byte $what") @@ -201,7 +196,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, } } DataType.BYTE -> { - when(what) { + when (what) { is ArrayIndexedExpression -> TODO("lsr sbyte $what") is DirectMemoryRead -> TODO("lsr sbyte $what") is RegisterExpr -> TODO("lsr sbyte $what") @@ -210,7 +205,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, } } DataType.UWORD -> { - when(what) { + when (what) { is ArrayIndexedExpression -> TODO("lsr uword $what") is IdentifierReference -> { val variable = asmgen.asmIdentifierName(what) @@ -220,7 +215,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, } } DataType.WORD -> { - when(what) { + when (what) { is ArrayIndexedExpression -> TODO("lsr sword $what") is IdentifierReference -> TODO("lsr sword $what") else -> throw AssemblyError("weird type") @@ -233,7 +228,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, // in-place val what = fcall.arglist.single() val dt = what.inferType(program) - when(dt.typeOrElse(DataType.STRUCT)) { + when (dt.typeOrElse(DataType.STRUCT)) { DataType.UBYTE -> { TODO("rol ubyte") } @@ -247,7 +242,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, // in-place val what = fcall.arglist.single() val dt = what.inferType(program) - when(dt.typeOrElse(DataType.STRUCT)) { + when (dt.typeOrElse(DataType.STRUCT)) { DataType.UBYTE -> { TODO("rol2 ubyte") } @@ -261,7 +256,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, // in-place val what = fcall.arglist.single() val dt = what.inferType(program) - when(dt.typeOrElse(DataType.STRUCT)) { + when (dt.typeOrElse(DataType.STRUCT)) { DataType.UBYTE -> { TODO("ror ubyte") } @@ -275,7 +270,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, // in-place val what = fcall.arglist.single() val dt = what.inferType(program) - when(dt.typeOrElse(DataType.STRUCT)) { + when (dt.typeOrElse(DataType.STRUCT)) { DataType.UBYTE -> { TODO("ror2 ubyte") } @@ -326,4 +321,3 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, } } - diff --git a/compiler/src/prog8/compiler/target/c64/codegen/ExpressionsAsmGen.kt b/compiler/src/prog8/compiler/target/c64/codegen/ExpressionsAsmGen.kt new file mode 100644 index 000000000..9bcfb9626 --- /dev/null +++ b/compiler/src/prog8/compiler/target/c64/codegen/ExpressionsAsmGen.kt @@ -0,0 +1,430 @@ +package prog8.compiler.target.c64.codegen + +import prog8.ast.Program +import prog8.ast.base.* +import prog8.ast.expressions.* +import prog8.compiler.target.c64.MachineDefinition +import prog8.compiler.toHex +import prog8.functions.BuiltinFunctions +import kotlin.math.absoluteValue + +internal class ExpressionsAsmGen(private val program: Program, private val asmgen: AsmGen) { + + internal fun translateExpression(expression: Expression) { + when(expression) { + is PrefixExpression -> translateExpression(expression) + is BinaryExpression -> translateExpression(expression) + is ArrayIndexedExpression -> translatePushFromArray(expression) + is TypecastExpression -> translateExpression(expression) + is AddressOf -> translateExpression(expression) + is DirectMemoryRead -> translateExpression(expression) + is NumericLiteralValue -> translateExpression(expression) + is RegisterExpr -> translateExpression(expression) + is IdentifierReference -> translateExpression(expression) + is FunctionCall -> { + val functionName = expression.target.nameInSource.last() + val builtinFunc = BuiltinFunctions[functionName] + if(builtinFunc!=null) { + asmgen.translateFunctioncallExpression(expression, builtinFunc) + } else { + asmgen.translateFunctionCall(expression) + val sub = expression.target.targetSubroutine(program.namespace)!! + val returns = sub.returntypes.zip(sub.asmReturnvaluesRegisters) + for((_, reg) in returns) { + if(!reg.stack) { + // result value in cpu or status registers, put it on the stack + if(reg.registerOrPair!=null) { + when(reg.registerOrPair) { + RegisterOrPair.A -> asmgen.out(" sta ${MachineDefinition.ESTACK_LO_HEX},x | dex") + RegisterOrPair.Y -> asmgen.out(" tya | sta ${MachineDefinition.ESTACK_LO_HEX},x | dex") + RegisterOrPair.AY -> asmgen.out(" sta ${MachineDefinition.ESTACK_LO_HEX},x | tya | sta ${MachineDefinition.ESTACK_HI_HEX},x | dex") + RegisterOrPair.X, RegisterOrPair.AX, RegisterOrPair.XY -> throw AssemblyError("can't push X register - use a variable") + } + } + // return value from a statusregister is not put on the stack, it should be acted on via a conditional branch such as if_cc + } + } + } + } + is ArrayLiteralValue, is StringLiteralValue -> TODO("string/array/struct assignment?") + is StructLiteralValue -> throw AssemblyError("struct literal value assignment should have been flattened") + is RangeExpr -> throw AssemblyError("range expression should have been changed into array values") + } + } + + private fun translateExpression(expr: TypecastExpression) { + translateExpression(expr.expression) + when(expr.expression.inferType(program).typeOrElse(DataType.STRUCT)) { + DataType.UBYTE -> { + when(expr.type) { + DataType.UBYTE, DataType.BYTE -> {} + DataType.UWORD, DataType.WORD -> asmgen.out(" lda #0 | sta ${MachineDefinition.ESTACK_HI_PLUS1_HEX},x") + DataType.FLOAT -> asmgen.out(" jsr c64flt.stack_ub2float") + in PassByReferenceDatatypes -> throw AssemblyError("cannot cast to a pass-by-reference datatype") + else -> throw AssemblyError("weird type") + } + } + DataType.BYTE -> { + when(expr.type) { + DataType.UBYTE, DataType.BYTE -> {} + DataType.UWORD, DataType.WORD -> asmgen.out(" lda ${MachineDefinition.ESTACK_LO_PLUS1_HEX},x | ${asmgen.signExtendAtoMsb("${MachineDefinition.ESTACK_HI_PLUS1_HEX},x")}") + DataType.FLOAT -> asmgen.out(" jsr c64flt.stack_b2float") + in PassByReferenceDatatypes -> throw AssemblyError("cannot cast to a pass-by-reference datatype") + else -> throw AssemblyError("weird type") + } + } + DataType.UWORD -> { + when(expr.type) { + DataType.BYTE, DataType.UBYTE -> {} + DataType.WORD, DataType.UWORD -> {} + DataType.FLOAT -> asmgen.out(" jsr c64flt.stack_uw2float") + in PassByReferenceDatatypes -> throw AssemblyError("cannot cast to a pass-by-reference datatype") + else -> throw AssemblyError("weird type") + } + } + DataType.WORD -> { + when(expr.type) { + DataType.BYTE, DataType.UBYTE -> {} + DataType.WORD, DataType.UWORD -> {} + DataType.FLOAT -> asmgen.out(" jsr c64flt.stack_w2float") + in PassByReferenceDatatypes -> throw AssemblyError("cannot cast to a pass-by-reference datatype") + else -> throw AssemblyError("weird type") + } + } + DataType.FLOAT -> { + when(expr.type) { + DataType.UBYTE -> asmgen.out(" jsr c64flt.stack_float2uw") + DataType.BYTE -> asmgen.out(" jsr c64flt.stack_float2w") + DataType.UWORD -> asmgen.out(" jsr c64flt.stack_float2uw") + DataType.WORD -> asmgen.out(" jsr c64flt.stack_float2w") + DataType.FLOAT -> {} + in PassByReferenceDatatypes -> throw AssemblyError("cannot cast to a pass-by-reference datatype") + else -> throw AssemblyError("weird type") + } + } + in PassByReferenceDatatypes -> throw AssemblyError("cannot case a pass-by-reference datatypes into something else") + else -> throw AssemblyError("weird type") + } + } + + private fun translateExpression(expr: AddressOf) { + val name = asmgen.asmIdentifierName(expr.identifier) + asmgen.out(" lda #<$name | sta ${MachineDefinition.ESTACK_LO_HEX},x | lda #>$name | sta ${MachineDefinition.ESTACK_HI_HEX},x | dex") + } + + private fun translateExpression(expr: DirectMemoryRead) { + when(expr.addressExpression) { + is NumericLiteralValue -> { + val address = (expr.addressExpression as NumericLiteralValue).number.toInt() + asmgen.out(" lda ${address.toHex()} | sta ${MachineDefinition.ESTACK_LO_HEX},x | dex") + } + is IdentifierReference -> { + val sourceName = asmgen.asmIdentifierName(expr.addressExpression as IdentifierReference) + asmgen.out(" lda $sourceName | sta ${MachineDefinition.ESTACK_LO_HEX},x | dex") + } + else -> { + translateExpression(expr.addressExpression) + asmgen.out(" jsr prog8_lib.read_byte_from_address") + asmgen.out(" sta ${MachineDefinition.ESTACK_LO_PLUS1_HEX},x") + } + } + } + + private fun translateExpression(expr: NumericLiteralValue) { + when(expr.type) { + DataType.UBYTE, DataType.BYTE -> asmgen.out(" lda #${expr.number.toHex()} | sta ${MachineDefinition.ESTACK_LO_HEX},x | dex") + DataType.UWORD, DataType.WORD -> asmgen.out(""" + lda #<${expr.number.toHex()} + sta ${MachineDefinition.ESTACK_LO_HEX},x + lda #>${expr.number.toHex()} + sta ${MachineDefinition.ESTACK_HI_HEX},x + dex + """) + DataType.FLOAT -> { + val floatConst = asmgen.getFloatConst(expr.number.toDouble()) + asmgen.out(" lda #<$floatConst | ldy #>$floatConst | jsr c64flt.push_float") + } + else -> throw AssemblyError("weird type") + } + } + + private fun translateExpression(expr: RegisterExpr) { + when(expr.register) { + Register.A -> asmgen.out(" sta ${MachineDefinition.ESTACK_LO_HEX},x | dex") + Register.X -> throw AssemblyError("cannot push X - use a variable instead of the X register") + Register.Y -> asmgen.out(" tya | sta ${MachineDefinition.ESTACK_LO_HEX},x | dex") + } + } + + private fun translateExpression(expr: IdentifierReference) { + val varname = asmgen.asmIdentifierName(expr) + when(expr.inferType(program).typeOrElse(DataType.STRUCT)) { + DataType.UBYTE, DataType.BYTE -> { + asmgen.out(" lda $varname | sta ${MachineDefinition.ESTACK_LO_HEX},x | dex") + } + DataType.UWORD, DataType.WORD, in ArrayDatatypes, in StringDatatypes -> { + // (for arrays and strings, push their address) + asmgen.out(" lda $varname | sta ${MachineDefinition.ESTACK_LO_HEX},x | lda $varname+1 | sta ${MachineDefinition.ESTACK_HI_HEX},x | dex") + } + DataType.FLOAT -> { + asmgen.out(" lda #<$varname | ldy #>$varname| jsr c64flt.push_float") + } + else -> throw AssemblyError("stack push weird variable type $expr") + } + } + + private val optimizedByteMultiplications = setOf(3,5,6,7,9,10,11,12,13,14,15,20,25,40) + private val optimizedWordMultiplications = setOf(3,5,6,7,9,10,12,15,20,25,40) + private val powerOfTwos = setOf(0,1,2,4,8,16,32,64,128,256) + + private fun translateExpression(expr: BinaryExpression) { + val leftIDt = expr.left.inferType(program) + val rightIDt = expr.right.inferType(program) + if(!leftIDt.isKnown || !rightIDt.isKnown) + throw AssemblyError("can't infer type of both expression operands") + + val leftDt = leftIDt.typeOrElse(DataType.STRUCT) + val rightDt = rightIDt.typeOrElse(DataType.STRUCT) + // see if we can apply some optimized routines + when(expr.operator) { + ">>" -> { + // bit-shifts are always by a constant number (for now) + translateExpression(expr.left) + val amount = expr.right.constValue(program)!!.number.toInt() + when (leftDt) { + DataType.UBYTE -> repeat(amount) { asmgen.out(" lsr ${MachineDefinition.ESTACK_LO_PLUS1_HEX},x") } + DataType.BYTE -> repeat(amount) { asmgen.out(" lda ${MachineDefinition.ESTACK_LO_PLUS1_HEX},x | asl a | ror ${MachineDefinition.ESTACK_LO_PLUS1_HEX},x") } + DataType.UWORD -> repeat(amount) { asmgen.out(" lsr ${MachineDefinition.ESTACK_HI_PLUS1_HEX},x | ror ${MachineDefinition.ESTACK_LO_PLUS1_HEX},x") } + DataType.WORD -> repeat(amount) { asmgen.out(" lda ${MachineDefinition.ESTACK_HI_PLUS1_HEX},x | asl a | ror ${MachineDefinition.ESTACK_HI_PLUS1_HEX},x | ror ${MachineDefinition.ESTACK_LO_PLUS1_HEX},x") } + else -> throw AssemblyError("weird type") + } + return + } + "<<" -> { + // bit-shifts are always by a constant number (for now) + translateExpression(expr.left) + val amount = expr.right.constValue(program)!!.number.toInt() + if (leftDt in ByteDatatypes) + repeat(amount) { asmgen.out(" asl ${MachineDefinition.ESTACK_LO_PLUS1_HEX},x") } + else + repeat(amount) { asmgen.out(" asl ${MachineDefinition.ESTACK_LO_PLUS1_HEX},x | rol ${MachineDefinition.ESTACK_HI_PLUS1_HEX},x") } + return + } + "*" -> { + val value = expr.right.constValue(program) + if(value!=null) { + if(rightDt in IntegerDatatypes) { + val amount = value.number.toInt() + if(amount in powerOfTwos) + printWarning("${expr.right.position} multiplication by power of 2 should have been optimized into a left shift instruction: $amount") + when(rightDt) { + DataType.UBYTE -> { + if(amount in optimizedByteMultiplications) { + translateExpression(expr.left) + asmgen.out(" jsr math.mul_byte_$amount") + return + } + } + DataType.BYTE -> { + if(amount in optimizedByteMultiplications) { + translateExpression(expr.left) + asmgen.out(" jsr math.mul_byte_$amount") + return + } + if(amount.absoluteValue in optimizedByteMultiplications) { + translateExpression(expr.left) + asmgen.out(" jsr prog8_lib.neg_b | jsr math.mul_byte_${amount.absoluteValue}") + return + } + } + DataType.UWORD -> { + if(amount in optimizedWordMultiplications) { + translateExpression(expr.left) + asmgen.out(" jsr math.mul_word_$amount") + return + } + } + DataType.WORD -> { + if(amount in optimizedWordMultiplications) { + translateExpression(expr.left) + asmgen.out(" jsr math.mul_word_$amount") + return + } + if(amount.absoluteValue in optimizedWordMultiplications) { + translateExpression(expr.left) + asmgen.out(" jsr prog8_lib.neg_w | jsr math.mul_word_${amount.absoluteValue}") + return + } + } + else -> {} + } + } + } + } + } + + // the general, non-optimized cases + translateExpression(expr.left) + translateExpression(expr.right) + if(leftDt!=rightDt) + throw AssemblyError("binary operator ${expr.operator} left/right dt not identical") // is this strictly required always? + when (leftDt) { + in ByteDatatypes -> translateBinaryOperatorBytes(expr.operator, leftDt) + in WordDatatypes -> translateBinaryOperatorWords(expr.operator, leftDt) + DataType.FLOAT -> translateBinaryOperatorFloats(expr.operator) + else -> throw AssemblyError("non-numerical datatype") + } + } + + private fun translateExpression(expr: PrefixExpression) { + translateExpression(expr.expression) + val type = expr.inferType(program).typeOrElse(DataType.STRUCT) + when(expr.operator) { + "+" -> {} + "-" -> { + when(type) { + in ByteDatatypes -> asmgen.out(" jsr prog8_lib.neg_b") + in WordDatatypes -> asmgen.out(" jsr prog8_lib.neg_w") + DataType.FLOAT -> asmgen.out(" jsr c64flt.neg_f") + else -> throw AssemblyError("weird type") + } + } + "~" -> { + when(type) { + in ByteDatatypes -> + asmgen.out(""" + lda ${MachineDefinition.ESTACK_LO_PLUS1_HEX},x + eor #255 + sta ${MachineDefinition.ESTACK_LO_PLUS1_HEX},x + """) + in WordDatatypes -> asmgen.out(" jsr prog8_lib.inv_word") + else -> throw AssemblyError("weird type") + } + } + "not" -> { + when(type) { + in ByteDatatypes -> asmgen.out(" jsr prog8_lib.not_byte") + in WordDatatypes -> asmgen.out(" jsr prog8_lib.not_word") + else -> throw AssemblyError("weird type") + } + } + else -> throw AssemblyError("invalid prefix operator ${expr.operator}") + } + } + + + private fun translatePushFromArray(arrayExpr: ArrayIndexedExpression) { + // assume *reading* from an array + val index = arrayExpr.arrayspec.index + val arrayDt = arrayExpr.identifier.targetVarDecl(program.namespace)!!.datatype + val arrayVarName = asmgen.asmIdentifierName(arrayExpr.identifier) + if(index is NumericLiteralValue) { + val elementDt = ArrayElementTypes.getValue(arrayDt) + val indexValue = index.number.toInt() * elementDt.memorySize() + when(elementDt) { + in ByteDatatypes -> { + asmgen.out(" lda $arrayVarName+$indexValue | sta ${MachineDefinition.ESTACK_LO_HEX},x | dex") + } + in WordDatatypes -> { + asmgen.out(" lda $arrayVarName+$indexValue | sta ${MachineDefinition.ESTACK_LO_HEX},x | lda $arrayVarName+$indexValue+1 | sta ${MachineDefinition.ESTACK_HI_HEX},x | dex") + } + DataType.FLOAT -> { + asmgen.out(" lda #<$arrayVarName+$indexValue | ldy #>$arrayVarName+$indexValue | jsr c64flt.push_float") + } + else -> throw AssemblyError("weird type") + } + } else { + asmgen.translateArrayIndexIntoA(arrayExpr) + asmgen.readAndPushArrayvalueWithIndexA(arrayDt, arrayExpr.identifier) + } + } + + private fun translateBinaryOperatorBytes(operator: String, types: DataType) { + when(operator) { + "**" -> throw AssemblyError("** operator requires floats") + "*" -> asmgen.out(" jsr prog8_lib.mul_byte") // the optimized routines should have been checked earlier + "/" -> asmgen.out(if(types==DataType.UBYTE) " jsr prog8_lib.idiv_ub" else " jsr prog8_lib.idiv_b") + "%" -> { + if(types==DataType.BYTE) + throw AssemblyError("remainder of signed integers is not properly defined/implemented, use unsigned instead") + asmgen.out(" jsr prog8_lib.remainder_ub") + } + "+" -> asmgen.out(""" + lda ${MachineDefinition.ESTACK_LO_PLUS2_HEX},x + clc + adc ${MachineDefinition.ESTACK_LO_PLUS1_HEX},x + inx + sta ${MachineDefinition.ESTACK_LO_PLUS1_HEX},x + """) + "-" -> asmgen.out(""" + lda ${MachineDefinition.ESTACK_LO_PLUS2_HEX},x + sec + sbc ${MachineDefinition.ESTACK_LO_PLUS1_HEX},x + inx + sta ${MachineDefinition.ESTACK_LO_PLUS1_HEX},x + """) + "<<", ">>" -> throw AssemblyError("bit-shifts not via stack") + "<" -> asmgen.out(if(types==DataType.UBYTE) " jsr prog8_lib.less_ub" else " jsr prog8_lib.less_b") + ">" -> asmgen.out(if(types==DataType.UBYTE) " jsr prog8_lib.greater_ub" else " jsr prog8_lib.greater_b") + "<=" -> asmgen.out(if(types==DataType.UBYTE) " jsr prog8_lib.lesseq_ub" else " jsr prog8_lib.lesseq_b") + ">=" -> asmgen.out(if(types==DataType.UBYTE) " jsr prog8_lib.greatereq_ub" else " jsr prog8_lib.greatereq_b") + "==" -> asmgen.out(" jsr prog8_lib.equal_b") + "!=" -> asmgen.out(" jsr prog8_lib.notequal_b") + "&" -> asmgen.out(" jsr prog8_lib.bitand_b") + "^" -> asmgen.out(" jsr prog8_lib.bitxor_b") + "|" -> asmgen.out(" jsr prog8_lib.bitor_b") + "and" -> asmgen.out(" jsr prog8_lib.and_b") + "or" -> asmgen.out(" jsr prog8_lib.or_b") + "xor" -> asmgen.out(" jsr prog8_lib.xor_b") + else -> throw AssemblyError("invalid operator $operator") + } + } + + private fun translateBinaryOperatorWords(operator: String, types: DataType) { + when(operator) { + "**" -> throw AssemblyError("** operator requires floats") + "*" -> asmgen.out(" jsr prog8_lib.mul_word") + "/" -> asmgen.out(if(types==DataType.UWORD) " jsr prog8_lib.idiv_uw" else " jsr prog8_lib.idiv_w") + "%" -> { + if(types==DataType.WORD) + throw AssemblyError("remainder of signed integers is not properly defined/implemented, use unsigned instead") + asmgen.out(" jsr prog8_lib.remainder_uw") + } + "+" -> asmgen.out(" jsr prog8_lib.add_w") + "-" -> asmgen.out(" jsr prog8_lib.sub_w") + "<<" -> throw AssemblyError("<< should not operate via stack") + ">>" -> throw AssemblyError(">> should not operate via stack") + "<" -> asmgen.out(if(types==DataType.UWORD) " jsr prog8_lib.less_uw" else " jsr prog8_lib.less_w") + ">" -> asmgen.out(if(types==DataType.UWORD) " jsr prog8_lib.greater_uw" else " jsr prog8_lib.greater_w") + "<=" -> asmgen.out(if(types==DataType.UWORD) " jsr prog8_lib.lesseq_uw" else " jsr prog8_lib.lesseq_w") + ">=" -> asmgen.out(if(types==DataType.UWORD) " jsr prog8_lib.greatereq_uw" else " jsr prog8_lib.greatereq_w") + "==" -> asmgen.out(" jsr prog8_lib.equal_w") + "!=" -> asmgen.out(" jsr prog8_lib.notequal_w") + "&" -> asmgen.out(" jsr prog8_lib.bitand_w") + "^" -> asmgen.out(" jsr prog8_lib.bitxor_w") + "|" -> asmgen.out(" jsr prog8_lib.bitor_w") + "and" -> asmgen.out(" jsr prog8_lib.and_w") + "or" -> asmgen.out(" jsr prog8_lib.or_w") + "xor" -> asmgen.out(" jsr prog8_lib.xor_w") + else -> throw AssemblyError("invalid operator $operator") + } + } + + private fun translateBinaryOperatorFloats(operator: String) { + when(operator) { + "**" -> asmgen.out(" jsr c64flt.pow_f") + "*" -> asmgen.out(" jsr c64flt.mul_f") + "/" -> asmgen.out(" jsr c64flt.div_f") + "+" -> asmgen.out(" jsr c64flt.add_f") + "-" -> asmgen.out(" jsr c64flt.sub_f") + "<" -> asmgen.out(" jsr c64flt.less_f") + ">" -> asmgen.out(" jsr c64flt.greater_f") + "<=" -> asmgen.out(" jsr c64flt.lesseq_f") + ">=" -> asmgen.out(" jsr c64flt.greatereq_f") + "==" -> asmgen.out(" jsr c64flt.equal_f") + "!=" -> asmgen.out(" jsr c64flt.notequal_f") + "%", "<<", ">>", "&", "^", "|", "and", "or", "xor" -> throw AssemblyError("requires integer datatype") + else -> throw AssemblyError("invalid operator $operator") + } + } +} diff --git a/compiler/src/prog8/compiler/target/c64/codegen/ForLoopsAsmGen.kt b/compiler/src/prog8/compiler/target/c64/codegen/ForLoopsAsmGen.kt new file mode 100644 index 000000000..741c5f1dd --- /dev/null +++ b/compiler/src/prog8/compiler/target/c64/codegen/ForLoopsAsmGen.kt @@ -0,0 +1,559 @@ +package prog8.compiler.target.c64.codegen + +import prog8.ast.Program +import prog8.ast.base.DataType +import prog8.ast.base.Register +import prog8.ast.expressions.IdentifierReference +import prog8.ast.expressions.RangeExpr +import prog8.ast.statements.AssignTarget +import prog8.ast.statements.Assignment +import prog8.ast.statements.ForLoop +import prog8.compiler.target.c64.MachineDefinition +import prog8.compiler.toHex +import kotlin.math.absoluteValue + + +internal class ForLoopsAsmGen(private val program: Program, private val asmgen: AsmGen) { + + internal fun translate(stmt: ForLoop) { + val iterableDt = stmt.iterable.inferType(program) + if(!iterableDt.isKnown) + throw AssemblyError("can't determine iterable dt") + when(stmt.iterable) { + is RangeExpr -> { + val range = (stmt.iterable as RangeExpr).toConstantIntegerRange() + if(range==null) { + translateForOverNonconstRange(stmt, iterableDt.typeOrElse(DataType.STRUCT), stmt.iterable as RangeExpr) + } else { + if (range.isEmpty()) + throw AssemblyError("empty range") + translateForOverConstRange(stmt, iterableDt.typeOrElse(DataType.STRUCT), range) + } + } + is IdentifierReference -> { + translateForOverIterableVar(stmt, iterableDt.typeOrElse(DataType.STRUCT), stmt.iterable as IdentifierReference) + } + else -> throw AssemblyError("can't iterate over ${stmt.iterable}") + } + } + + private fun translateForOverNonconstRange(stmt: ForLoop, iterableDt: DataType, range: RangeExpr) { + val loopLabel = asmgen.makeLabel("for_loop") + val endLabel = asmgen.makeLabel("for_end") + val continueLabel = asmgen.makeLabel("for_continue") + val counterLabel = asmgen.makeLabel("for_counter") + asmgen.loopEndLabels.push(endLabel) + asmgen.loopContinueLabels.push(continueLabel) + val stepsize=range.step.constValue(program)?.number + when (stepsize) { + 1 -> { + when(iterableDt) { + DataType.ARRAY_B, DataType.ARRAY_UB -> { + if (stmt.loopRegister != null) { + // loop register over range + if(stmt.loopRegister!= Register.A) + throw AssemblyError("can only use A") + asmgen.translateExpression(range.to) + asmgen.translateExpression(range.from) + asmgen.out(""" + inx + lda ${MachineDefinition.ESTACK_LO_HEX},x + sta $loopLabel+1 + inx + lda ${MachineDefinition.ESTACK_LO_HEX},x + sec + sbc $loopLabel+1 + adc #0 + sta $counterLabel +$loopLabel lda #0 ; modified""") + asmgen.translate(stmt.body) + asmgen.out(""" +$continueLabel dec $counterLabel + beq $endLabel + inc $loopLabel+1 + jmp $loopLabel +$counterLabel .byte 0 +$endLabel""") + } else { + // loop over byte range via loopvar + val varname = asmgen.asmIdentifierName(stmt.loopVar!!) + asmgen.translateExpression(range.to) + asmgen.translateExpression(range.from) + asmgen.out(""" + inx + lda ${MachineDefinition.ESTACK_LO_HEX},x + sta $varname + inx + lda ${MachineDefinition.ESTACK_LO_HEX},x + sec + sbc $varname + adc #0 + sta $counterLabel +$loopLabel""") + asmgen.translate(stmt.body) + asmgen.out(""" +$continueLabel dec $counterLabel + beq $endLabel + inc $varname + jmp $loopLabel +$counterLabel .byte 0 +$endLabel""") + } + } + DataType.ARRAY_UW, DataType.ARRAY_W -> { + asmgen.translateExpression(range.to) + asmgen.out(" inc ${MachineDefinition.ESTACK_LO_HEX}+1,x | bne + | inc ${MachineDefinition.ESTACK_HI_HEX}+1,x |+ ") + val varname = asmgen.asmIdentifierName(stmt.loopVar!!) + val assignLoopvar = Assignment(AssignTarget(null, stmt.loopVar, null, null, stmt.loopVar!!.position), + null, range.from, range.position) + assignLoopvar.linkParents(stmt) + asmgen.translate(assignLoopvar) + asmgen.out(loopLabel) + asmgen.translate(stmt.body) + asmgen.out(""" + inc $varname + bne + + inc $varname+1 ++ lda ${MachineDefinition.ESTACK_HI_HEX}+1,x + cmp $varname+1 + bne + + lda ${MachineDefinition.ESTACK_LO_HEX}+1,x + cmp $varname + beq $endLabel ++ jmp $loopLabel +$endLabel inx""") + } + else -> throw AssemblyError("range expression can only be byte or word") + } + } + -1 -> { + when(iterableDt){ + DataType.ARRAY_B, DataType.ARRAY_UB -> { + if (stmt.loopRegister != null) { + // loop register over range + if(stmt.loopRegister!= Register.A) + throw AssemblyError("can only use A") + asmgen.translateExpression(range.from) + asmgen.translateExpression(range.to) + asmgen.out(""" + inx + lda ${MachineDefinition.ESTACK_LO_HEX}+1,x + sta $loopLabel+1 + sec + sbc ${MachineDefinition.ESTACK_LO_HEX},x + adc #0 + sta $counterLabel + inx +$loopLabel lda #0 ; modified""") + asmgen.translate(stmt.body) + asmgen.out(""" +$continueLabel dec $counterLabel + beq $endLabel + dec $loopLabel+1 + jmp $loopLabel +$counterLabel .byte 0 +$endLabel""") + } else { + // loop over byte range via loopvar + val varname = asmgen.asmIdentifierName(stmt.loopVar!!) + asmgen.translateExpression(range.from) + asmgen.translateExpression(range.to) + asmgen.out(""" + inx + lda ${MachineDefinition.ESTACK_LO_HEX}+1,x + sta $varname + sec + sbc ${MachineDefinition.ESTACK_LO_HEX},x + adc #0 + sta $counterLabel + inx +$loopLabel""") + asmgen.translate(stmt.body) + asmgen.out(""" +$continueLabel dec $counterLabel + beq $endLabel + dec $varname + jmp $loopLabel +$counterLabel .byte 0 +$endLabel""") + } + } + DataType.ARRAY_UW, DataType.ARRAY_W -> { + asmgen.translateExpression(range.to) + asmgen.out(""" + lda ${MachineDefinition.ESTACK_LO_HEX}+1,x + bne + + dec ${MachineDefinition.ESTACK_HI_HEX}+1,x ++ dec ${MachineDefinition.ESTACK_LO_HEX}+1,x + """) + val varname = asmgen.asmIdentifierName(stmt.loopVar!!) + val assignLoopvar = Assignment(AssignTarget(null, stmt.loopVar, null, null, stmt.loopVar!!.position), + null, range.from, range.position) + assignLoopvar.linkParents(stmt) + asmgen.translate(assignLoopvar) + asmgen.out(loopLabel) + asmgen.translate(stmt.body) + asmgen.out(""" + lda $varname + bne + + dec $varname+1 ++ dec $varname + lda ${MachineDefinition.ESTACK_HI_HEX}+1,x + cmp $varname+1 + bne + + lda ${MachineDefinition.ESTACK_LO_HEX}+1,x + cmp $varname + beq $endLabel ++ jmp $loopLabel +$endLabel inx""") + } + else -> throw AssemblyError("range expression can only be byte or word") + } + } + else -> when (iterableDt) { + DataType.ARRAY_UB, DataType.ARRAY_B -> TODO("non-const forloop bytes, step >1: $stepsize") + DataType.ARRAY_UW, DataType.ARRAY_W -> TODO("non-const forloop words, step >1: $stepsize") + else -> throw AssemblyError("range expression can only be byte or word") + } + } + asmgen.loopEndLabels.pop() + asmgen.loopContinueLabels.pop() + } + + private fun translateForOverIterableVar(stmt: ForLoop, iterableDt: DataType, ident: IdentifierReference) { + val loopLabel = asmgen.makeLabel("for_loop") + val endLabel = asmgen.makeLabel("for_end") + val continueLabel = asmgen.makeLabel("for_continue") + asmgen.loopEndLabels.push(endLabel) + asmgen.loopContinueLabels.push(continueLabel) + val iterableName = asmgen.asmIdentifierName(ident) + val decl = ident.targetVarDecl(program.namespace)!! + when(iterableDt) { + DataType.STR, DataType.STR_S -> { + if(stmt.loopRegister!=null && stmt.loopRegister!= Register.A) + throw AssemblyError("can only use A") + asmgen.out(""" + lda #<$iterableName + ldy #>$iterableName + sta $loopLabel+1 + sty $loopLabel+2 +$loopLabel lda ${65535.toHex()} ; modified + beq $endLabel""") + if(stmt.loopVar!=null) + asmgen.out(" sta ${asmgen.asmIdentifierName(stmt.loopVar!!)}") + asmgen.translate(stmt.body) + asmgen.out(""" +$continueLabel inc $loopLabel+1 + bne $loopLabel + inc $loopLabel+2 + bne $loopLabel +$endLabel""") + } + DataType.ARRAY_UB, DataType.ARRAY_B -> { + val length = decl.arraysize!!.size()!! + if(stmt.loopRegister!=null && stmt.loopRegister!= Register.A) + throw AssemblyError("can only use A") + val counterLabel = asmgen.makeLabel("for_counter") + val modifiedLabel = asmgen.makeLabel("for_modified") + asmgen.out(""" + lda #<$iterableName + ldy #>$iterableName + sta $modifiedLabel+1 + sty $modifiedLabel+2 + ldy #0 +$loopLabel sty $counterLabel +$modifiedLabel lda ${65535.toHex()},y ; modified""") + if(stmt.loopVar!=null) + asmgen.out(" sta ${asmgen.asmIdentifierName(stmt.loopVar!!)}") + asmgen.translate(stmt.body) + asmgen.out(""" +$continueLabel ldy $counterLabel + iny + cpy #${length and 255} + beq $endLabel + jmp $loopLabel +$counterLabel .byte 0 +$endLabel""") + } + DataType.ARRAY_W, DataType.ARRAY_UW -> { + val length = decl.arraysize!!.size()!! * 2 + if(stmt.loopRegister!=null) + throw AssemblyError("can't use register to loop over words") + val counterLabel = asmgen.makeLabel("for_counter") + val modifiedLabel = asmgen.makeLabel("for_modified") + val modifiedLabel2 = asmgen.makeLabel("for_modified2") + val loopvarName = asmgen.asmIdentifierName(stmt.loopVar!!) + asmgen.out(""" + lda #<$iterableName + ldy #>$iterableName + sta $modifiedLabel+1 + sty $modifiedLabel+2 + lda #<$iterableName+1 + ldy #>$iterableName+1 + sta $modifiedLabel2+1 + sty $modifiedLabel2+2 + ldy #0 +$loopLabel sty $counterLabel +$modifiedLabel lda ${65535.toHex()},y ; modified + sta $loopvarName +$modifiedLabel2 lda ${65535.toHex()},y ; modified + sta $loopvarName+1""") + asmgen.translate(stmt.body) + asmgen.out(""" +$continueLabel ldy $counterLabel + iny + iny + cpy #${length and 255} + beq $endLabel + jmp $loopLabel +$counterLabel .byte 0 +$endLabel""") + } + DataType.ARRAY_F -> { + throw AssemblyError("for loop with floating point variables is not supported") + } + else -> throw AssemblyError("can't iterate over $iterableDt") + } + asmgen.loopEndLabels.pop() + asmgen.loopContinueLabels.pop() + } + + private fun translateForOverConstRange(stmt: ForLoop, iterableDt: DataType, range: IntProgression) { + // TODO: optimize loop code when the range is < 256 iterations, don't need a separate counter in such cases + val loopLabel = asmgen.makeLabel("for_loop") + val endLabel = asmgen.makeLabel("for_end") + val continueLabel = asmgen.makeLabel("for_continue") + asmgen.loopEndLabels.push(endLabel) + asmgen.loopContinueLabels.push(continueLabel) + when(iterableDt) { + DataType.ARRAY_B, DataType.ARRAY_UB -> { + val counterLabel = asmgen.makeLabel("for_counter") + if(stmt.loopRegister!=null) { + + // loop register over range + + if(stmt.loopRegister!= Register.A) + throw AssemblyError("can only use A") + when { + range.step==1 -> { + // step = 1 + asmgen.out(""" + lda #${range.first} + sta $loopLabel+1 + lda #${range.last-range.first+1 and 255} + sta $counterLabel +$loopLabel lda #0 ; modified""") + asmgen.translate(stmt.body) + asmgen.out(""" +$continueLabel dec $counterLabel + beq $endLabel + inc $loopLabel+1 + jmp $loopLabel +$counterLabel .byte 0 +$endLabel""") + } + range.step==-1 -> { + // step = -1 + asmgen.out(""" + lda #${range.first} + sta $loopLabel+1 + lda #${range.first-range.last+1 and 255} + sta $counterLabel +$loopLabel lda #0 ; modified """) + asmgen.translate(stmt.body) + asmgen.out(""" +$continueLabel dec $counterLabel + beq $endLabel + dec $loopLabel+1 + jmp $loopLabel +$counterLabel .byte 0 +$endLabel""") + } + range.step >= 2 -> { + // step >= 2 + asmgen.out(""" + lda #${(range.last-range.first) / range.step + 1} + sta $counterLabel + lda #${range.first} +$loopLabel pha""") + asmgen.translate(stmt.body) + asmgen.out(""" +$continueLabel pla + dec $counterLabel + beq $endLabel + clc + adc #${range.step} + jmp $loopLabel +$counterLabel .byte 0 +$endLabel""") + } + else -> { + // step <= -2 + asmgen.out(""" + lda #${(range.first-range.last) / range.step.absoluteValue + 1} + sta $counterLabel + lda #${range.first} +$loopLabel pha""") + asmgen.translate(stmt.body) + asmgen.out(""" +$continueLabel pla + dec $counterLabel + beq $endLabel + sec + sbc #${range.step.absoluteValue} + jmp $loopLabel +$counterLabel .byte 0 +$endLabel""") + } + } + + } else { + + // loop over byte range via loopvar + val varname = asmgen.asmIdentifierName(stmt.loopVar!!) + when { + range.step==1 -> { + // step = 1 + asmgen.out(""" + lda #${range.first} + sta $varname + lda #${range.last-range.first+1 and 255} + sta $counterLabel +$loopLabel""") + asmgen.translate(stmt.body) + asmgen.out(""" +$continueLabel dec $counterLabel + beq $endLabel + inc $varname + jmp $loopLabel +$counterLabel .byte 0 +$endLabel""") + } + range.step==-1 -> { + // step = -1 + asmgen.out(""" + lda #${range.first} + sta $varname + lda #${range.first-range.last+1 and 255} + sta $counterLabel +$loopLabel""") + asmgen.translate(stmt.body) + asmgen.out(""" +$continueLabel dec $counterLabel + beq $endLabel + dec $varname + jmp $loopLabel +$counterLabel .byte 0 +$endLabel""") + } + range.step >= 2 -> { + // step >= 2 + asmgen.out(""" + lda #${(range.last-range.first) / range.step + 1} + sta $counterLabel + lda #${range.first} + sta $varname +$loopLabel""") + asmgen.translate(stmt.body) + asmgen.out(""" +$continueLabel dec $counterLabel + beq $endLabel + lda $varname + clc + adc #${range.step} + sta $varname + jmp $loopLabel +$counterLabel .byte 0 +$endLabel""") + } + else -> { + // step <= -2 + asmgen.out(""" + lda #${(range.first-range.last) / range.step.absoluteValue + 1} + sta $counterLabel + lda #${range.first} + sta $varname +$loopLabel""") + asmgen.translate(stmt.body) + asmgen.out(""" +$continueLabel dec $counterLabel + beq $endLabel + lda $varname + sec + sbc #${range.step.absoluteValue} + sta $varname + jmp $loopLabel +$counterLabel .byte 0 +$endLabel""") + } + } + } + } + DataType.ARRAY_W, DataType.ARRAY_UW -> { + // loop over word range via loopvar + val varname = asmgen.asmIdentifierName(stmt.loopVar!!) + when { + range.step == 1 -> { + // word, step = 1 + val lastValue = range.last+1 + asmgen.out(""" + lda #<${range.first} + ldy #>${range.first} + sta $varname + sty $varname+1 +$loopLabel""") + asmgen.translate(stmt.body) + asmgen.out(""" +$continueLabel inc $varname + bne + + inc $varname+1 ++ lda $varname + cmp #<$lastValue + bne + + lda $varname+1 + cmp #>$lastValue + beq $endLabel ++ jmp $loopLabel +$endLabel""") + } + range.step == -1 -> { + // word, step = 1 + val lastValue = range.last-1 + asmgen.out(""" + lda #<${range.first} + ldy #>${range.first} + sta $varname + sty $varname+1 +$loopLabel""") + asmgen.translate(stmt.body) + asmgen.out(""" +$continueLabel lda $varname + bne + + dec $varname+1 ++ dec $varname + lda $varname + cmp #<$lastValue + bne + + lda $varname+1 + cmp #>$lastValue + beq $endLabel ++ jmp $loopLabel +$endLabel""") + } + range.step >= 2 -> { + // word, step >= 2 + TODO("for, word, step>=2") + } + else -> { + // step <= -2 + TODO("for, word, step<=-2") + } + } + } + else -> throw AssemblyError("range expression can only be byte or word") + } + asmgen.loopEndLabels.pop() + asmgen.loopContinueLabels.pop() + } + +} diff --git a/compiler/src/prog8/compiler/target/c64/codegen/FunctionCallAsmGen.kt b/compiler/src/prog8/compiler/target/c64/codegen/FunctionCallAsmGen.kt new file mode 100644 index 000000000..1d8deda7d --- /dev/null +++ b/compiler/src/prog8/compiler/target/c64/codegen/FunctionCallAsmGen.kt @@ -0,0 +1,235 @@ +package prog8.compiler.target.c64.codegen + +import prog8.ast.IFunctionCall +import prog8.ast.Program +import prog8.ast.base.* +import prog8.ast.expressions.* +import prog8.ast.statements.AssignTarget +import prog8.ast.statements.Subroutine +import prog8.ast.statements.SubroutineParameter +import prog8.compiler.target.c64.MachineDefinition +import prog8.compiler.toHex + +internal class FunctionCallAsmGen(private val program: Program, private val asmgen: AsmGen) { + + internal fun translateFunctionCall(stmt: IFunctionCall) { + // output the code to setup the parameters and perform the actual call + // does NOT output the code to deal with the result values! + val sub = stmt.target.targetSubroutine(program.namespace) ?: throw AssemblyError("undefined subroutine ${stmt.target}") + if(Register.X in sub.asmClobbers) + asmgen.out(" stx c64.SCRATCH_ZPREGX") // we only save X for now (required! is the eval stack pointer), screw A and Y... + + val subName = asmgen.asmIdentifierName(stmt.target) + if(stmt.arglist.isNotEmpty()) { + for(arg in sub.parameters.withIndex().zip(stmt.arglist)) { + translateFuncArguments(arg.first, arg.second, sub) + } + } + asmgen.out(" jsr $subName") + + if(Register.X in sub.asmClobbers) + 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) + 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()) { + // pass parameter via a variable + 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 arguments?") + 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 arguments?") + 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 { + // 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) + } + 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 + bcs ++ ++ clc ++ +""") + } + is RegisterExpr -> { + when(value.register) { + Register.A -> asmgen.out(" cmp #0") + Register.X -> asmgen.out(" txa") + Register.Y -> asmgen.out(" tya") + } + asmgen.out(""" + beq + + sec + bcs ++ ++ clc ++ +""") + } + else -> { + asmgen.translateExpression(value) + asmgen.out(""" + inx + lda ${MachineDefinition.ESTACK_LO_HEX},x + beq + + sec + bcs ++ ++ clc ++ +""") + } + } + } + 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 ${MachineDefinition.ESTACK_LO_HEX},x") + RegisterOrPair.X -> throw AssemblyError("can't pop into X register - use a variable instead") + RegisterOrPair.Y -> asmgen.out(" inx | ldy ${MachineDefinition.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 -> {} + } + } + 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) + 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 ${MachineDefinition.ESTACK_LO_HEX},x | ldy ${MachineDefinition.ESTACK_HI_HEX},x") + } + } + } + } + } + } + + private fun argumentTypeCompatible(argType: DataType, paramType: DataType): Boolean { + if(argType isAssignableTo paramType) + return true + + // we have a special rule for some types. + // strings are assignable to UWORD, for example, and vice versa + if(argType in StringDatatypes && paramType==DataType.UWORD) + return true + if(argType==DataType.UWORD && paramType in StringDatatypes) + return true + + return false + } +} diff --git a/compiler/src/prog8/compiler/target/c64/codegen/PostIncrDecrAsmGen.kt b/compiler/src/prog8/compiler/target/c64/codegen/PostIncrDecrAsmGen.kt new file mode 100644 index 000000000..6089bb173 --- /dev/null +++ b/compiler/src/prog8/compiler/target/c64/codegen/PostIncrDecrAsmGen.kt @@ -0,0 +1,149 @@ +package prog8.compiler.target.c64.codegen + +import prog8.ast.Program +import prog8.ast.base.* +import prog8.ast.expressions.IdentifierReference +import prog8.ast.expressions.NumericLiteralValue +import prog8.ast.expressions.RegisterExpr +import prog8.ast.statements.PostIncrDecr +import prog8.compiler.target.c64.MachineDefinition +import prog8.compiler.toHex + +internal class PostIncrDecrAsmGen(private val program: Program, private val asmgen: AsmGen) { + internal fun translate(stmt: PostIncrDecr) { + val incr = stmt.operator=="++" + val targetIdent = stmt.target.identifier + val targetMemory = stmt.target.memoryAddress + val targetArrayIdx = stmt.target.arrayindexed + val targetRegister = stmt.target.register + when { + targetRegister!=null -> { + when(targetRegister) { + Register.A -> { + if(incr) + asmgen.out(" clc | adc #1 ") + else + asmgen.out(" sec | sbc #1 ") + } + Register.X -> { + if(incr) asmgen.out(" inx") else asmgen.out(" dex") + } + Register.Y -> { + if(incr) asmgen.out(" iny") else asmgen.out(" dey") + } + } + } + targetIdent!=null -> { + val what = asmgen.asmIdentifierName(targetIdent) + val dt = stmt.target.inferType(program, stmt).typeOrElse(DataType.STRUCT) + when (dt) { + in ByteDatatypes -> asmgen.out(if (incr) " inc $what" else " dec $what") + in WordDatatypes -> { + if(incr) + asmgen.out(" inc $what | bne + | inc $what+1 |+") + else + asmgen.out(""" + lda $what + bne + + dec $what+1 ++ dec $what +""") + } + DataType.FLOAT -> { + asmgen.out(" lda #<$what | ldy #>$what") + asmgen.out(if(incr) " jsr c64flt.inc_var_f" else " jsr c64flt.dec_var_f") + } + else -> throw AssemblyError("need numeric type") + } + } + targetMemory!=null -> { + val addressExpr = targetMemory.addressExpression + when (addressExpr) { + is NumericLiteralValue -> { + val what = addressExpr.number.toHex() + asmgen.out(if(incr) " inc $what" else " dec $what") + } + is IdentifierReference -> { + val what = asmgen.asmIdentifierName(addressExpr) + asmgen.out(if(incr) " inc $what" else " dec $what") + } + else -> throw AssemblyError("weird target type $targetMemory") + } + } + targetArrayIdx!=null -> { + val index = targetArrayIdx.arrayspec.index + val what = asmgen.asmIdentifierName(targetArrayIdx.identifier) + val arrayDt = targetArrayIdx.identifier.inferType(program).typeOrElse(DataType.STRUCT) + val elementDt = ArrayElementTypes.getValue(arrayDt) + when(index) { + is NumericLiteralValue -> { + val indexValue = index.number.toInt() * elementDt.memorySize() + when(elementDt) { + in ByteDatatypes -> asmgen.out(if (incr) " inc $what+$indexValue" else " dec $what+$indexValue") + in WordDatatypes -> { + if(incr) + asmgen.out(" inc $what+$indexValue | bne + | inc $what+$indexValue+1 |+") + else + asmgen.out(""" + lda $what+$indexValue + bne + + dec $what+$indexValue+1 ++ dec $what+$indexValue +""") + } + DataType.FLOAT -> { + asmgen.out(" lda #<$what+$indexValue | ldy #>$what+$indexValue") + asmgen.out(if(incr) " jsr c64flt.inc_var_f" else " jsr c64flt.dec_var_f") + } + else -> throw AssemblyError("need numeric type") + } + } + is RegisterExpr -> { + // TODO optimize common cases + asmgen.translateArrayIndexIntoA(targetArrayIdx) + incrDecrArrayvalueWithIndexA(incr, arrayDt, what) + } + is IdentifierReference -> { + // TODO optimize common cases + asmgen.translateArrayIndexIntoA(targetArrayIdx) + incrDecrArrayvalueWithIndexA(incr, arrayDt, what) + } + else -> { + // TODO optimize common cases + asmgen.translateArrayIndexIntoA(targetArrayIdx) + incrDecrArrayvalueWithIndexA(incr, arrayDt, what) + } + } + } + else -> throw AssemblyError("weird target type ${stmt.target}") + } + } + + private fun incrDecrArrayvalueWithIndexA(incr: Boolean, arrayDt: DataType, arrayVarName: String) { + asmgen.out(" stx ${MachineDefinition.C64Zeropage.SCRATCH_REG_X} | tax") + when(arrayDt) { + DataType.STR, DataType.STR_S, + DataType.ARRAY_UB, DataType.ARRAY_B -> { + asmgen.out(if(incr) " inc $arrayVarName,x" else " dec $arrayVarName,x") + } + DataType.ARRAY_UW, DataType.ARRAY_W -> { + if(incr) + asmgen.out(" inc $arrayVarName,x | bne + | inc $arrayVarName+1,x |+") + else + asmgen.out(""" + lda $arrayVarName,x + bne + + dec $arrayVarName+1,x ++ dec $arrayVarName +""") + } + DataType.ARRAY_F -> { + asmgen.out(" lda #<$arrayVarName | ldy #>$arrayVarName") + asmgen.out(if(incr) " jsr c64flt.inc_indexed_var_f" else " jsr c64flt.dec_indexed_var_f") + } + else -> throw AssemblyError("weird array dt") + } + asmgen.out(" ldx ${MachineDefinition.C64Zeropage.SCRATCH_REG_X}") + } + +} diff --git a/examples/cube3d-sprites.p8 b/examples/cube3d-sprites.p8 index 46e461afa..9fe132179 100644 --- a/examples/cube3d-sprites.p8 +++ b/examples/cube3d-sprites.p8 @@ -1,6 +1,8 @@ %import c64lib %import c64utils +; TODO: some optimizer breaks this.. the 3d sorting seems broken? or maybe more? runs fine without optimization + spritedata $2000 { ; this memory block contains the sprite data ; it must start on an address aligned to 64 bytes.