diff --git a/compiler/res/prog8lib/c64flt.p8 b/compiler/res/prog8lib/c64flt.p8 index 56465414a..416876158 100644 --- a/compiler/res/prog8lib/c64flt.p8 +++ b/compiler/res/prog8lib/c64flt.p8 @@ -52,7 +52,7 @@ asmsub MOVMF (uword mflpt @ XY) clobbers(A,Y) = $bbd4 ; store fac1 to memory ; fac1-> signed word in Y/A (might throw ILLEGAL QUANTITY) ; (tip: use c64flt.FTOSWRDAY to get A/Y output; lo/hi switched to normal little endian order) -asmsub FTOSWORDYA () clobbers(X) -> ubyte @ Y, ubyte @ A = $b1aa +asmsub FTOSWORDYA () clobbers(X) -> ubyte @ Y, ubyte @ A = $b1aa ; note: calls AYINT. ; fac1 -> unsigned word in Y/A (might throw ILLEGAL QUANTITY) (result also in $14/15) ; (tip: use c64flt.GETADRAY to get A/Y output; lo/hi switched to normal little endian order) @@ -196,8 +196,8 @@ sub print_f (float value) { ; ---- prints the floating point value (without a newline) using basic rom routines. %asm {{ stx c64.SCRATCH_ZPREGX - lda #print_f_value + lda #value jsr MOVFM ; load float into fac1 jsr FOUT ; fac1 to string in A/Y jsr c64.STROUT ; print string in A/Y @@ -310,7 +310,7 @@ stack_uw2float .proc jmp push_fac1_as_result .pend -stack_float2w .proc +stack_float2w .proc ; also used for float2b jsr pop_float_fac1 stx c64.SCRATCH_ZPREGX jsr AYINT @@ -323,7 +323,7 @@ stack_float2w .proc rts .pend -stack_float2uw .proc +stack_float2uw .proc ; also used for float2ub jsr pop_float_fac1 stx c64.SCRATCH_ZPREGX jsr GETADR diff --git a/compiler/src/prog8/ast/base/Base.kt b/compiler/src/prog8/ast/base/Base.kt index d712db068..c89d4664b 100644 --- a/compiler/src/prog8/ast/base/Base.kt +++ b/compiler/src/prog8/ast/base/Base.kt @@ -1,6 +1,7 @@ package prog8.ast.base import prog8.ast.Node +import prog8.compiler.target.c64.MachineDefinition /**************************** AST Data classes ****************************/ @@ -25,10 +26,10 @@ enum class DataType { infix fun isAssignableTo(targetType: DataType) = // what types are assignable to others without loss of precision? when(this) { - UBYTE -> targetType in setOf(UBYTE, UWORD, WORD, FLOAT) - BYTE -> targetType in setOf(BYTE, UBYTE, UWORD, WORD, FLOAT) + UBYTE -> targetType in setOf(UBYTE, WORD, UWORD, FLOAT) + BYTE -> targetType in setOf(BYTE, WORD, FLOAT) UWORD -> targetType in setOf(UWORD, FLOAT) - WORD -> targetType in setOf(WORD, UWORD, FLOAT) + WORD -> targetType in setOf(WORD, FLOAT) FLOAT -> targetType == FLOAT STR -> targetType == STR || targetType==STR_S STR_S -> targetType == STR || targetType==STR_S @@ -52,6 +53,16 @@ enum class DataType { in WordDatatypes -> other in WordDatatypes else -> false } + + fun memorySize(): Int { + return when(this) { + in ByteDatatypes -> 1 + in WordDatatypes -> 2 + FLOAT -> MachineDefinition.Mflpt5.MemorySize + in PassByReferenceDatatypes -> 2 + else -> -9999999 + } + } } enum class Register { diff --git a/compiler/src/prog8/ast/processing/AstIdentifiersChecker.kt b/compiler/src/prog8/ast/processing/AstIdentifiersChecker.kt index b114c869a..ab0a64b26 100644 --- a/compiler/src/prog8/ast/processing/AstIdentifiersChecker.kt +++ b/compiler/src/prog8/ast/processing/AstIdentifiersChecker.kt @@ -133,7 +133,7 @@ internal class AstIdentifiersChecker(private val program: Program) : IAstModifyi subroutine.parameters .filter { it.name !in namesInSub } .forEach { - val vardecl = VarDecl(VarDeclType.VAR, it.type, ZeropageWish.DONTCARE, null, it.name, null, null, + val vardecl = VarDecl(VarDeclType.VAR, it.type, ZeropageWish.NOT_IN_ZEROPAGE, null, it.name, null, null, isArray = false, autogeneratedDontRemove = true, position = subroutine.position) vardecl.linkParents(subroutine) subroutine.statements.add(0, vardecl) diff --git a/compiler/src/prog8/compiler/Main.kt b/compiler/src/prog8/compiler/Main.kt index e6bb02984..5fae61d9c 100644 --- a/compiler/src/prog8/compiler/Main.kt +++ b/compiler/src/prog8/compiler/Main.kt @@ -64,7 +64,7 @@ fun compileProgram(filepath: Path, programAst.removeNopsFlattenAnonScopes() // if you want to print the AST, do it before shuffling the statements around below - //printAst(programAst) + printAst(programAst) programAst.reorderStatements() // reorder statements and add type casts, to please the compiler later diff --git a/compiler/src/prog8/compiler/target/c64/codegen/SimpleAsm.kt b/compiler/src/prog8/compiler/target/c64/codegen/SimpleAsm.kt index 732091feb..c7736fc53 100644 --- a/compiler/src/prog8/compiler/target/c64/codegen/SimpleAsm.kt +++ b/compiler/src/prog8/compiler/target/c64/codegen/SimpleAsm.kt @@ -459,8 +459,8 @@ internal fun simpleInstr2Asm(ins: Instruction, block: IntermediateProgram.Progra Opcode.CAST_B_TO_F -> " jsr c64flt.stack_b2float" Opcode.CAST_UW_TO_F -> " jsr c64flt.stack_uw2float" Opcode.CAST_W_TO_F -> " jsr c64flt.stack_w2float" - Opcode.CAST_F_TO_UB -> " jsr c64flt.stack_float2uw" - Opcode.CAST_F_TO_B -> " jsr c64flt.stack_float2w" + Opcode.CAST_F_TO_UB -> " jsr c64flt.stack_float2ub" + Opcode.CAST_F_TO_B -> " jsr c64flt.stack_float2b" Opcode.CAST_F_TO_UW -> " jsr c64flt.stack_float2uw" Opcode.CAST_F_TO_W -> " jsr c64flt.stack_float2w" Opcode.CAST_UB_TO_UW, Opcode.CAST_UB_TO_W -> " lda #0 | sta $ESTACK_HI_PLUS1_HEX,x" // clear the msb diff --git a/compiler/src/prog8/compiler/target/c64/codegen2/AsmGen2.kt b/compiler/src/prog8/compiler/target/c64/codegen2/AsmGen2.kt index 0dbe985bc..a4b070726 100644 --- a/compiler/src/prog8/compiler/target/c64/codegen2/AsmGen2.kt +++ b/compiler/src/prog8/compiler/target/c64/codegen2/AsmGen2.kt @@ -1,6 +1,9 @@ package prog8.compiler.target.c64.codegen2 +import prog8.ast.IFunctionCall +import prog8.ast.Node import prog8.ast.Program +import prog8.ast.antlr.escape import prog8.ast.base.* import prog8.ast.base.initvarsSubName import prog8.ast.expressions.* @@ -8,9 +11,16 @@ 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.ESTACK_LO_HEX +import prog8.compiler.target.c64.MachineDefinition.ESTACK_LO_PLUS1_HEX +import prog8.compiler.target.c64.MachineDefinition.ESTACK_HI_HEX +import prog8.compiler.target.c64.MachineDefinition.ESTACK_HI_PLUS1_HEX +import prog8.compiler.target.c64.Petscii import prog8.compiler.target.c64.codegen.optimizeAssembly +import prog8.functions.BuiltinFunctions import java.io.File import java.util.* +import kotlin.math.absoluteValue internal class AssemblyError(msg: String) : RuntimeException(msg) @@ -24,6 +34,7 @@ internal class AsmGen2(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) internal fun compileToAssembly(optimize: Boolean): AssemblyProgram { assemblyLines.clear() @@ -123,8 +134,10 @@ internal class AsmGen2(val program: Program, // the global list of all floating point constants for the whole program out("; global float constants") for (flt in globalFloatConsts) { - val floatFill = makeFloatFill(MachineDefinition.Mflpt5.fromNumber(flt.key)) - out("${flt.value}\t.byte $floatFill ; float ${flt.key}") + val mflpt5 = MachineDefinition.Mflpt5.fromNumber(flt.key) + val floatFill = makeFloatFill(mflpt5) + val floatvalue = flt.key + out("${flt.value}\t.byte $floatFill ; float $floatvalue") } } @@ -136,10 +149,11 @@ internal class AsmGen2(val program: Program, out("* = ${block.address.toHex()}") } + outputSourceLine(block) zeropagevars2asm(block.statements) memdefs2asm(block.statements) vardecls2asm(block.statements) - out("") + out("\n; subroutines in this block") // first translate regular statements, and then put the subroutines at the end. val (subroutine, stmts) = block.statements.partition { it is Subroutine } @@ -149,14 +163,20 @@ internal class AsmGen2(val program: Program, out("\n\t.pend\n") // TODO not if force_output? } - private fun out(str: String, splitlines: Boolean = true) { + private fun outputSourceLine(node: Node) { + out(" ;\tsrc line: ${node.position.file}:${node.position.line}") + } + + internal fun out(str: String, splitlines: Boolean = true) { + val fragment = (if(" | " in str) str.replace("|", "\n") else str).trim('\n') + if (splitlines) { - for (line in str.split('\n')) { + for (line in fragment.split('\n')) { val trimmed = if (line.startsWith(' ')) "\t" + line.trim() else line.trim() // trimmed = trimmed.replace(Regex("^\\+\\s+"), "+\t") // sanitize local label indentation assemblyLines.add(trimmed) } - } else assemblyLines.add(str) + } else assemblyLines.add(fragment) } private fun makeFloatFill(flt: MachineDefinition.Mflpt5): String { @@ -168,10 +188,25 @@ internal class AsmGen2(val program: Program, return "$b0, $b1, $b2, $b3, $b4" } + private fun encodeStr(str: String, dt: DataType): List { + return when(dt) { + DataType.STR -> { + val bytes = Petscii.encodePetscii(str, true) + bytes.plus(0) + } + DataType.STR_S -> { + val bytes = Petscii.encodeScreencode(str, true) + bytes.plus(0) + } + else -> throw prog8.compiler.target.c64.codegen.AssemblyError("invalid str type") + } + } + private fun zeropagevars2asm(statements: List) { out("; vars allocated on zeropage") val variables = statements.filterIsInstance().filter { it.type==VarDeclType.VAR } for(variable in variables) { + // should NOT allocate subroutine parameters on the zero page val fullName = variable.scopedname val zpVar = allocatedZeropageVariables[fullName] if(zpVar==null) { @@ -205,10 +240,14 @@ internal class AsmGen2(val program: Program, DataType.WORD -> out("${decl.name}\t.sint 0") DataType.FLOAT -> out("${decl.name}\t.byte 0,0,0,0,0 ; float") DataType.STRUCT -> {} // is flattened - DataType.STR -> TODO() - DataType.STR_S -> TODO() + DataType.STR, DataType.STR_S -> { + val string = (decl.value as ReferenceLiteralValue).str!! + val bytes = encodeStr(string, decl.datatype).map { "$" + it.toString(16).padStart(2, '0') } + out("${decl.name}\t; ${decl.datatype} \"${escape(string).replace("\u0000", "")}\"") + for (chunk in bytes.chunked(16)) + out(" .byte " + chunk.joinToString()) + } DataType.ARRAY_UB -> { - // unsigned integer byte arraysize val data = makeArrayFillDataUnsigned(decl) if (data.size <= 16) out("${decl.name}\t.byte ${data.joinToString()}") @@ -218,10 +257,46 @@ internal class AsmGen2(val program: Program, out(" .byte " + chunk.joinToString()) } } - DataType.ARRAY_B -> TODO() - DataType.ARRAY_UW -> TODO() - DataType.ARRAY_W -> TODO() - DataType.ARRAY_F -> TODO() + DataType.ARRAY_B -> { + val data = makeArrayFillDataSigned(decl) + if (data.size <= 16) + out("${decl.name}\t.char ${data.joinToString()}") + else { + out(decl.name) + for (chunk in data.chunked(16)) + out(" .char " + chunk.joinToString()) + } + } + DataType.ARRAY_UW -> { + val data = makeArrayFillDataUnsigned(decl) + if (data.size <= 16) + out("${decl.name}\t.word ${data.joinToString()}") + else { + out(decl.name) + for (chunk in data.chunked(16)) + out(" .word " + chunk.joinToString()) + } + } + DataType.ARRAY_W -> { + val data = makeArrayFillDataSigned(decl) + if (data.size <= 16) + out("${decl.name}\t.sint ${data.joinToString()}") + else { + out(decl.name) + for (chunk in data.chunked(16)) + out(" .sint " + chunk.joinToString()) + } + } + DataType.ARRAY_F -> { + val array = (decl.value as ReferenceLiteralValue).array!! + val floatFills = array.map { + val number = (it as NumericLiteralValue).number + makeFloatFill(MachineDefinition.Mflpt5.fromNumber(number)) + } + out(decl.name) + for (f in array.zip(floatFills)) + out(" .byte ${f.second} ; float ${f.first}") + } } } @@ -259,7 +334,45 @@ internal class AsmGen2(val program: Program, private fun makeArrayFillDataUnsigned(decl: VarDecl): List { val array = (decl.value as ReferenceLiteralValue).array!! - return array.map { (it as NumericLiteralValue).number.toString() } + return when { + decl.datatype == DataType.ARRAY_UB -> + // byte array can never contain pointer-to types, so treat values as all integers + array.map { + val number = (it as NumericLiteralValue).number.toInt() + "$"+number.toString(16).padStart(2, '0') + } + decl.datatype== DataType.ARRAY_UW -> array.map { + val number = (it as NumericLiteralValue).number.toInt() + // TODO word array with address-references + "$"+number.toString(16).padStart(4, '0') + } + else -> throw AssemblyError("invalid arraysize type") + } + } + + private fun makeArrayFillDataSigned(decl: VarDecl): List { + val array = (decl.value as ReferenceLiteralValue).array!! + return when { + decl.datatype == DataType.ARRAY_UB -> + // byte array can never contain pointer-to types, so treat values as all integers + array.map { + val number = (it as NumericLiteralValue).number.toInt() + val hexnum = number.absoluteValue.toString(16).padStart(2, '0') + if(number>=0) + "$$hexnum" + else + "-$$hexnum" + } + decl.datatype== DataType.ARRAY_UW -> array.map { + val number = (it as NumericLiteralValue).number.toInt() + val hexnum = number.absoluteValue.toString(16).padStart(4, '0') + if(number>=0) + "$$hexnum" + else + "-$$hexnum" + } + else -> throw AssemblyError("invalid arraysize type") + } } private fun getFloatConst(number: Double): String { @@ -271,6 +384,14 @@ internal class AsmGen2(val program: Program, return newName } + private fun signExtendAtoMsb(destination: String) = + """ + ora #$7f + bmi + + lda #0 ++ sta $destination + """ + private fun asmIdentifierName(identifier: IdentifierReference): String { val name = if(identifier.memberOfStruct(program.namespace)!=null) { identifier.targetVarDecl(program.namespace)!!.name @@ -308,13 +429,28 @@ internal class AsmGen2(val program: Program, } private fun translate(stmt: Statement) { + outputSourceLine(stmt) when(stmt) { is VarDecl, is StructDecl, is NopStatement -> {} is Directive -> translate(stmt) is Return -> translate(stmt) is Subroutine -> translate(stmt) is InlineAssembly -> translate(stmt) - is FunctionCallStatement -> translate(stmt) + is FunctionCallStatement -> { + val functionName = stmt.target.nameInSource.last() + val builtinFunc = BuiltinFunctions[functionName] + if(builtinFunc!=null) { + builtinFunctionsAsmGen.translateFunctioncallStatement(stmt, builtinFunc) + } else { + translateSubroutineCall(stmt) + // discard any results from the stack: + val returns = stmt.target.targetSubroutine(program.namespace)!!.returntypes + for(t in returns) { + if (t in IntegerDatatypes || t in PassByReferenceDatatypes) out(" inx") + else if (t == DataType.FLOAT) out(" inx | inx | inx") + } + } + } is Assignment -> translate(stmt) is Jump -> translate(stmt) is PostIncrDecr -> translate(stmt) @@ -333,6 +469,80 @@ internal class AsmGen2(val program: Program, } } + private fun translateSubroutineCall(stmt: IFunctionCall) { + val subName = stmt.target.nameInSource.joinToString(".") + if(stmt.arglist.isNotEmpty()) { + val sub = stmt.target.targetSubroutine(program.namespace)!! + for(arg in sub.parameters.withIndex().zip(stmt.arglist)) { + if(arg.first.value.type!=arg.second.inferType(program)) + throw AssemblyError("argument type mismatch") + if(sub.asmParameterRegisters.isEmpty()) { + // pass arg via a variable + val paramVar = arg.first.value + val scopedParamVar = (sub.scopedname+"."+paramVar.name).split(".") + val target = AssignTarget(null, IdentifierReference(scopedParamVar, sub.position), null, null, sub.position) + target.linkParents(stmt as Node) + val literal = arg.second as? NumericLiteralValue + when { + literal!=null -> { + // optimize when the argument is a constant literal + when(arg.first.value.type) { + in ByteDatatypes -> assignByteConstant(target, literal.number.toShort()) + in WordDatatypes -> assignWordConstant(target, literal.number.toInt()) + DataType.FLOAT -> assignFloatConstant(target, literal.number.toDouble()) + in PassByReferenceDatatypes-> TODO( "str/array/struct sub arg") + else -> throw AssemblyError("weird arg datatype") + } + } + arg.second is IdentifierReference -> { + // optimize when the argument is a variable + when (arg.first.value.type) { + in ByteDatatypes -> assignByteVariable(target, arg.second as IdentifierReference) + in WordDatatypes -> assignWordVariable(target, arg.second as IdentifierReference) + DataType.FLOAT -> assignFloatVariable(target, arg.second as IdentifierReference) + in PassByReferenceDatatypes -> TODO("str/array/struct sub arg") + else -> throw AssemblyError("weird arg datatype") + } + } + else -> TODO("non-constant sub arg $arg") + } + } else { + // pass arg via a register parameter + val paramRegister = sub.asmParameterRegisters[arg.first.index] + val statusflag = paramRegister.statusflag + val register = paramRegister.registerOrPair + val stack = paramRegister.stack + when { + stack==true -> TODO("stack param") + statusflag!=null -> { + if (statusflag == Statusflag.Pc) TODO("carry flag param") + else throw AssemblyError("can only use Carry as status flag parameter") + } + register!=null -> { + val target = AssignTarget(Register.valueOf(register.name), null, null, null, sub.position) + target.linkParents(stmt as Node) + val literal = arg.second as? NumericLiteralValue + if(literal!=null) { + // optimize when the argument is a constant literal + when(register) { + RegisterOrPair.A, + RegisterOrPair.X, + RegisterOrPair.Y -> assignByteConstant(target, literal.number.toShort()) + RegisterOrPair.AX -> TODO("register A+X param $literal") + RegisterOrPair.AY -> TODO("register A+Y param $literal") + RegisterOrPair.XY -> TODO("register X+Y param $literal") + } + } else { + TODO("register param") + } + } + } + } + } + } + out(" jsr $subName") + } + private fun translate(stmt: Label) { out("${stmt.name}") } @@ -347,7 +557,7 @@ internal class AsmGen2(val program: Program, val instruction = branchInstruction(stmt.condition, false) out(" $instruction ${getJumpTarget(jump)}") } else { - TODO("$stmt") + TODO("branch $stmt") } } @@ -376,26 +586,50 @@ internal class AsmGen2(val program: Program, } private fun translate(stmt: ForLoop) { - out("; for $stmt") + out("; TODO for $stmt") // TODO } private fun translate(stmt: PostIncrDecr) { + val incr = stmt.operator=="++" when { stmt.target.register!=null -> { when(stmt.target.register!!) { - Register.A -> out(""" - clc - adc #1 - """) - Register.X -> out(" inx") - Register.Y -> out(" iny") + 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") + } } } stmt.target.identifier!=null -> { - val targetName = asmIdentifierName(stmt.target.identifier!!) - out(" inc $targetName") + val what = asmIdentifierName(stmt.target.identifier!!) + out(if(incr) " inc $what" else " dec $what") } - else -> TODO("postincrdecr $stmt") + stmt.target.memoryAddress!=null -> { + val target = stmt.target.memoryAddress!!.addressExpression + when (target) { + is NumericLiteralValue -> { + val what = target.number.toHex() + out(if(incr) " inc $what" else " dec $what") + } + is IdentifierReference -> { + val what = target.nameInSource.joinToString(".") + out(if(incr) " inc $what" else " dec $what") + } + else -> throw AssemblyError("weird target type $target") + } + } + stmt.target.arrayindexed!=null -> { + TODO("postincrdecr array element ${stmt.target.arrayindexed}") + } + else -> throw AssemblyError("weird target type ${stmt.target}") } } @@ -413,13 +647,14 @@ internal class AsmGen2(val program: Program, } private fun translate(ret: Return) { - if(ret.value!=null) { - TODO("$ret value") - } + ret.value?.let { translateExpression(it) } out(" rts") } private fun translate(sub: Subroutine) { + out("") + outputSourceLine(sub) + if(sub.isAsmSubroutine) { if(sub.asmAddress!=null) return // already done at the memvars section @@ -433,7 +668,9 @@ internal class AsmGen2(val program: Program, out("${sub.name}\t.proc") zeropagevars2asm(sub.statements) memdefs2asm(sub.statements) + out("; statements") sub.statements.forEach{ translate(it) } + out("; variables") vardecls2asm(sub.statements) out(" .pend\n") } @@ -444,14 +681,6 @@ internal class AsmGen2(val program: Program, assemblyLines.add(assembly) } - private fun translate(call: FunctionCallStatement) { - if(call.arglist.isEmpty()) { - out(" jsr ${call.target.nameInSource.joinToString(".")}") - } else { - TODO("call $call") - } - } - private fun translate(assign: Assignment) { if(assign.aug_op!=null) throw AssemblyError("aug-op assignments should have been transformed to normal ones") @@ -460,7 +689,7 @@ internal class AsmGen2(val program: Program, is NumericLiteralValue -> { val numVal = assign.value as NumericLiteralValue when(numVal.type) { - DataType.UBYTE, DataType.BYTE -> assignByteConstant(assign.target, numVal.number.toInt()) + DataType.UBYTE, DataType.BYTE -> assignByteConstant(assign.target, numVal.number.toShort()) DataType.UWORD, DataType.WORD -> assignWordConstant(assign.target, numVal.number.toInt()) DataType.FLOAT -> assignFloatConstant(assign.target, numVal.number.toDouble()) DataType.STR -> TODO() @@ -481,7 +710,7 @@ internal class AsmGen2(val program: Program, when(type) { DataType.UBYTE, DataType.BYTE -> assignByteVariable(assign.target, assign.value as IdentifierReference) DataType.UWORD, DataType.WORD -> assignWordVariable(assign.target, assign.value as IdentifierReference) - DataType.FLOAT -> TODO() + DataType.FLOAT -> assignFloatVariable(assign.target, assign.value as IdentifierReference) DataType.STR -> TODO() DataType.STR_S -> TODO() DataType.ARRAY_UB -> TODO() @@ -509,7 +738,7 @@ internal class AsmGen2(val program: Program, } else -> { translateExpression(read.addressExpression) - out("; read memory byte from result and put that in ${assign.target}") // TODO + out("; TODO read memory byte from result and put that in ${assign.target}") // TODO } } } @@ -550,19 +779,65 @@ internal class AsmGen2(val program: Program, } private fun translateExpression(expr: ArrayIndexedExpression) { - out("; evaluate arrayindexed ${expr}") - } - - private fun translateExpression(expr: FunctionCall) { - out("; evaluate funccall ${expr}") + out("; TODO evaluate arrayindexed ${expr}") // TODO } private fun translateExpression(expr: TypecastExpression) { translateExpression(expr.expression) - out("; typecast to ${expr.type}") + when(expr.expression.inferType(program)!!) { + 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_HI_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") + } } - private fun translateExpression(expression: Expression) { + internal fun translateExpression(expression: Expression) { when(expression) { is PrefixExpression -> translateExpression(expression) is BinaryExpression -> translateExpression(expression) @@ -573,7 +848,15 @@ internal class AsmGen2(val program: Program, is NumericLiteralValue -> translateExpression(expression) is RegisterExpr -> translateExpression(expression) is IdentifierReference -> translateExpression(expression) - is FunctionCall -> translateExpression(expression) + is FunctionCall -> { + val functionName = expression.target.nameInSource.last() + val builtinFunc = BuiltinFunctions[functionName] + if(builtinFunc!=null) { + builtinFunctionsAsmGen.translateFunctioncallExpression(expression, builtinFunc) + } else { + translateSubroutineCall(expression) + } + } is ReferenceLiteralValue -> 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") @@ -581,38 +864,105 @@ internal class AsmGen2(val program: Program, } private fun translateExpression(expr: AddressOf) { - out("; take address of ${expr}") + out("; TODO take address of ${expr}") // TODO } private fun translateExpression(expr: DirectMemoryRead) { - out("; memread ${expr}") + out("; TODO memread ${expr}") // TODO } private fun translateExpression(expr: NumericLiteralValue) { - out("; literalvalue ${expr}") + 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") + } } private fun translateExpression(expr: RegisterExpr) { - out("; register value ${expr}") + when(expr.register) { + Register.A -> out(" sta $ESTACK_LO_HEX,x | dex") + Register.X -> throw AssemblyError("cannot push X") + Register.Y -> out(" tya | sta $ESTACK_LO_HEX,x | dex") + } } private fun translateExpression(expr: IdentifierReference) { - out("; identifier value ${expr}") + val varname = expr.nameInSource.joinToString(".") + when(expr.inferType(program)!!) { + DataType.UBYTE, DataType.BYTE -> { + out(" lda $varname | sta $ESTACK_LO_HEX,x | dex") + } + DataType.UWORD, DataType.WORD -> { + 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") + } } private fun translateExpression(expr: BinaryExpression) { translateExpression(expr.left) translateExpression(expr.right) - out("; evaluate binary ${expr.operator}") + out("; TODO evaluate binary ${expr.operator}") // TODO } private fun translateExpression(expr: PrefixExpression) { translateExpression(expr.expression) - out("; evaluate prefix ${expr.operator}") + out("; TODO evaluate prefix ${expr.operator}") // TODO } private fun assignEvalResult(target: AssignTarget) { - out("; put result in $target") + when { + target.register!=null -> { + out(" inx | ld${target.register.name.toLowerCase()} $ESTACK_LO_HEX,x ") + } + target.identifier!=null -> { + val targetName = target.identifier.nameInSource.joinToString(".") + val targetDt = target.identifier.inferType(program)!! + 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("; TODO put result in $target") // TODO + } + target.arrayindexed!=null -> { + out("; TODO put result in $target") // TODO + } + else -> throw AssemblyError("weird assignment target $target") + } } private fun assignAddressOf(target: AssignTarget, name: IdentifierReference, scopedname: String) { @@ -664,6 +1014,28 @@ internal class AsmGen2(val program: Program, } } + private fun assignFloatVariable(target: AssignTarget, variable: IdentifierReference) { + val sourceName = variable.nameInSource.joinToString(".") + when { + target.identifier!=null -> { + val targetName = asmIdentifierName(target.identifier) + 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 + """) + } + else -> TODO("assign float to $target") + } + } + private fun assignByteVariable(target: AssignTarget, variable: IdentifierReference) { val sourceName = variable.nameInSource.joinToString(".") when { @@ -706,38 +1078,43 @@ internal class AsmGen2(val program: Program, } } } - else -> out("; assign register $register to $target") + else -> out("; TODO assign register $register to $target") // TODO } } private fun assignWordConstant(target: AssignTarget, word: Int) { if(target.identifier!=null) { val targetName = asmIdentifierName(target.identifier) - // TODO optimize case where lsb = msb - out(""" - lda #<${word.toHex()} - ldy #>${word.toHex()} - sta $targetName - sty $targetName+1 - """) + 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 + """) + } } else { - out("; assign byte $word to $target") + out("; TODO assign byte $word to $target") // TODO } } - private fun assignByteConstant(target: AssignTarget, byte: Int) { + private fun assignByteConstant(target: AssignTarget, byte: Short) { when { target.register!=null -> { out(" ld${target.register.name.toLowerCase()} #${byte.toHex()}") } target.identifier!=null -> { val targetName = asmIdentifierName(target.identifier) - out(""" - lda #${byte.toHex()} - sta $targetName - """) + out(" lda #${byte.toHex()} | sta $targetName ") } - else -> out("; assign byte $byte to $target") + else -> out("; TODO assign byte $byte to $target") // TODO } } @@ -755,7 +1132,7 @@ internal class AsmGen2(val program: Program, sta $targetName+4 """) } else { - out("; assign float 0.0 to $target") + out("; TODO assign float 0.0 to $target") // TODO } } else { // non-zero value @@ -775,7 +1152,7 @@ internal class AsmGen2(val program: Program, sta $targetName+4 """) } else { - out("; assign float $float ($constFloat) to $target") + out("; TODO assign float $float ($constFloat) to $target") // TODO } } } @@ -793,7 +1170,7 @@ internal class AsmGen2(val program: Program, sta $targetName """) } - else -> TODO() + else -> TODO("assign memory byte $target") } } else if(identifier!=null) { @@ -818,7 +1195,7 @@ internal class AsmGen2(val program: Program, sta $targetName """) } - else -> TODO() + else -> TODO("assign memory byte $target") } } } diff --git a/compiler/src/prog8/compiler/target/c64/codegen2/BuiltinFunctionsAsmGen.kt b/compiler/src/prog8/compiler/target/c64/codegen2/BuiltinFunctionsAsmGen.kt new file mode 100644 index 000000000..ea915f01b --- /dev/null +++ b/compiler/src/prog8/compiler/target/c64/codegen2/BuiltinFunctionsAsmGen.kt @@ -0,0 +1,68 @@ +package prog8.compiler.target.c64.codegen2 + +import prog8.ast.IFunctionCall +import prog8.ast.Program +import prog8.ast.base.WordDatatypes +import prog8.ast.expressions.FunctionCall +import prog8.ast.expressions.IdentifierReference +import prog8.ast.expressions.NumericLiteralValue +import prog8.ast.statements.FunctionCallStatement +import prog8.ast.statements.Subroutine +import prog8.compiler.target.c64.MachineDefinition +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_HI_HEX +import prog8.compiler.target.c64.MachineDefinition.ESTACK_HI_PLUS1_HEX +import prog8.compiler.CompilationOptions +import prog8.compiler.Zeropage +import prog8.functions.BuiltinFunctions +import prog8.functions.FunctionSignature + +internal class BuiltinFunctionsAsmGen(private val program: Program, + private val options: CompilationOptions, + private val zeropage: Zeropage, + private val asmgen: AsmGen2) { + + internal fun translateFunctioncallExpression(fcall: FunctionCall, func: FunctionSignature) { + translateFunctioncall(fcall, func, false) + } + + internal fun translateFunctioncallStatement(fcall: FunctionCallStatement, func: FunctionSignature) { + translateFunctioncall(fcall, func, true) + } + + private fun translateFunctioncall(fcall: IFunctionCall, func: FunctionSignature, discardResult: Boolean) { + val functionName = fcall.target.nameInSource.last() + if(discardResult) { + if(func.pure) + return // can just ignore the whole function call altogether + else + throw AssemblyError("discarding result of non-pure function $fcall") + } + + when(functionName) { + "msb" -> { + val arg = fcall.arglist.single() + if(arg.inferType(program) !in WordDatatypes) + throw AssemblyError("msb required word argument") + if(arg is NumericLiteralValue) + throw AssemblyError("should have been const-folded") + if(arg is IdentifierReference) { + val sourceName = arg.nameInSource.joinToString(".") + asmgen.out(" lda $sourceName+1 | sta $ESTACK_LO_HEX,x | dex") + } else { + asmgen.translateExpression(arg) + asmgen.out(" lda $ESTACK_HI_PLUS1_HEX,x | sta $ESTACK_LO_PLUS1_HEX,x") + } + } + "mkword" -> { + asmgen.translateExpression(fcall.arglist[0]) + asmgen.translateExpression(fcall.arglist[1]) + asmgen.out(" inx | lda $ESTACK_LO_HEX,x | sta $ESTACK_HI_PLUS1_HEX,x") + } + else -> TODO("builtin function $functionName") + } + } + +} + diff --git a/compiler/src/prog8/optimizer/StatementOptimizer.kt b/compiler/src/prog8/optimizer/StatementOptimizer.kt index c7ef0426f..ce1929bb4 100644 --- a/compiler/src/prog8/optimizer/StatementOptimizer.kt +++ b/compiler/src/prog8/optimizer/StatementOptimizer.kt @@ -379,10 +379,10 @@ internal class StatementOptimizer(private val program: Program, private val opti printWarning("condition is always true", whileLoop.position) if(hasContinueOrBreak(whileLoop.body)) return whileLoop - val label = Label("__back", whileLoop.condition.position) + val label = Label("_prog8_back", whileLoop.condition.position) whileLoop.body.statements.add(0, label) whileLoop.body.statements.add(Jump(null, - IdentifierReference(listOf("__back"), whileLoop.condition.position), + IdentifierReference(listOf("_prog8_back"), whileLoop.condition.position), null, whileLoop.condition.position)) optimizationsDone++ return whileLoop.body diff --git a/examples/romfloats.p8 b/examples/romfloats.p8 new file mode 100644 index 000000000..ce06041f5 --- /dev/null +++ b/examples/romfloats.p8 @@ -0,0 +1,74 @@ +%import c64flt +%zeropage basicsafe +%option enable_floats + +~ main { + + sub start() { + + float f1 + + ; these are all floating point constants defined in the ROM so no allocation required + ; TODO actually read these from ROM + + f1 = 3.141592653589793 + c64flt.print_f(f1) + c64.CHROUT('\n') + + f1 = -32768.0 + c64flt.print_f(f1) + c64.CHROUT('\n') + + f1 = 1.0 + c64flt.print_f(f1) + c64.CHROUT('\n') + + f1 = 0.7071067811865476 + c64flt.print_f(f1) + c64.CHROUT('\n') + + f1 = 1.4142135623730951 + c64flt.print_f(f1) + c64.CHROUT('\n') + + f1 = -0.5 + c64flt.print_f(f1) + c64.CHROUT('\n') + + f1 = 0.6931471805599453 + c64flt.print_f(f1) + c64.CHROUT('\n') + + f1 = 10.0 + c64flt.print_f(f1) + c64.CHROUT('\n') + + f1 = 1.0e9 + c64flt.print_f(f1) + c64.CHROUT('\n') + + f1 = 0.5 + c64flt.print_f(f1) + c64.CHROUT('\n') + + f1 = 1.4426950408889634 + c64flt.print_f(f1) + c64.CHROUT('\n') + + f1 = 1.5707963267948966 + c64flt.print_f(f1) + c64.CHROUT('\n') + + f1 = 6.283185307179586 + c64flt.print_f(f1) + c64.CHROUT('\n') + + f1 = 0.25 + c64flt.print_f(f1) + c64.CHROUT('\n') + + f1 = 0.0 + c64flt.print_f(f1) + c64.CHROUT('\n') + } +} diff --git a/examples/test.p8 b/examples/test.p8 index 74f08de61..ce06041f5 100644 --- a/examples/test.p8 +++ b/examples/test.p8 @@ -1,4 +1,3 @@ -%import c64utils %import c64flt %zeropage basicsafe %option enable_floats @@ -7,11 +6,69 @@ sub start() { - if_z goto start - if_pos goto start - if_cc goto start - if_nz goto start + float f1 + ; these are all floating point constants defined in the ROM so no allocation required + ; TODO actually read these from ROM + + f1 = 3.141592653589793 + c64flt.print_f(f1) + c64.CHROUT('\n') + + f1 = -32768.0 + c64flt.print_f(f1) + c64.CHROUT('\n') + + f1 = 1.0 + c64flt.print_f(f1) + c64.CHROUT('\n') + + f1 = 0.7071067811865476 + c64flt.print_f(f1) + c64.CHROUT('\n') + + f1 = 1.4142135623730951 + c64flt.print_f(f1) + c64.CHROUT('\n') + + f1 = -0.5 + c64flt.print_f(f1) + c64.CHROUT('\n') + + f1 = 0.6931471805599453 + c64flt.print_f(f1) + c64.CHROUT('\n') + + f1 = 10.0 + c64flt.print_f(f1) + c64.CHROUT('\n') + + f1 = 1.0e9 + c64flt.print_f(f1) + c64.CHROUT('\n') + + f1 = 0.5 + c64flt.print_f(f1) + c64.CHROUT('\n') + + f1 = 1.4426950408889634 + c64flt.print_f(f1) + c64.CHROUT('\n') + + f1 = 1.5707963267948966 + c64flt.print_f(f1) + c64.CHROUT('\n') + + f1 = 6.283185307179586 + c64flt.print_f(f1) + c64.CHROUT('\n') + + f1 = 0.25 + c64flt.print_f(f1) + c64.CHROUT('\n') + + f1 = 0.0 + c64flt.print_f(f1) + c64.CHROUT('\n') } - }