diff --git a/compiler/examples/test.p8 b/compiler/examples/test.p8 index 14837de17..6a137061a 100644 --- a/compiler/examples/test.p8 +++ b/compiler/examples/test.p8 @@ -11,43 +11,44 @@ sub start() { memory byte mb = $c000 memory word mw = $c002 - byte b + + byte b = -100 ubyte ub = $c4 ubyte ub2 = $c4 uword uw = $c500 uword uw2 = $c502 - word ww + word ww = -30000 + float f1 = 1.23456 + float f2 = -999.999e22 + float f3 = -999.999e22 + float f4 = -999.999e22 - mub=5 - muw=4444 - mb=-100 - mw=-23333 + str s1 = "hallo" + str_p s2 = "hallo p" + str_s s3 = "hallo s" + str_ps s4 = "hallo ps" - -; b++ -; A++ -; XY++ -; mub++ -; ub++ -; uw++ -; ww++ -; mb++ -; mw++ -; -; b-- -; A-- -; XY-- -; mub-- -; ub-- -; uw-- -; ww-- -; mb-- -; mw-- + byte[4] array1 = -99 + ubyte[4] array2 = 244 + word[4] array3 = -999 + uword[4] array4 = 44444 + float[4] array5 = [11.11, 22.22, 33.33, 44.44] + byte[3,7] matrix1 = [1,-2,3,-4,5,-6,1,-2,3,-4,5,-6,1,-2,3,-4,5,-6,22,33,44] + ubyte[3,7] matrix2 = [11,22,33,44,55,66, 11,22,33,44,55,66, 11,22,33,44,55,66, 55,66,77] return + sub nested () { + byte b + uword uw + } +} + +sub second() { + byte b + uword uw } } diff --git a/compiler/src/prog8/CompilerMain.kt b/compiler/src/prog8/CompilerMain.kt index 1ce6e49b4..0d33b058c 100644 --- a/compiler/src/prog8/CompilerMain.kt +++ b/compiler/src/prog8/CompilerMain.kt @@ -106,7 +106,7 @@ fun main(args: Array) { stackvmFile.close() println("StackVM program code written to '$stackVmFilename'") - val assembly = AsmGen(compilerOptions).compileToAssembly(intermediate) + val assembly = AsmGen(compilerOptions, intermediate, heap).compileToAssembly() assembly.assemble(compilerOptions) val endTime = System.currentTimeMillis() diff --git a/compiler/src/prog8/ast/AstChecker.kt b/compiler/src/prog8/ast/AstChecker.kt index 07f4ce704..6268b1fe1 100644 --- a/compiler/src/prog8/ast/AstChecker.kt +++ b/compiler/src/prog8/ast/AstChecker.kt @@ -866,20 +866,24 @@ class AstChecker(private val namespace: INameScope, } DataType.MATRIX_UB, DataType.MATRIX_B -> { // value can only be a single byte, or a byte array (which represents the matrix) - if(value.type==targetDt) { + if(value.type==targetDt || + (targetDt==DataType.MATRIX_UB && value.type==DataType.ARRAY_UB) || + (targetDt==DataType.MATRIX_B && value.type==DataType.ARRAY_B)) { val arraySpecSize = arrayspec.size() if(arraySpecSize!=null && arraySpecSize>0) { val constX = arrayspec.x.constValue(namespace, heap) - val constY = arrayspec.y!!.constValue(namespace, heap) - if (constX?.asIntegerValue == null || constY?.asIntegerValue == null) + val constY = arrayspec.y?.constValue(namespace, heap) + if (constX?.asIntegerValue == null || (constY!=null && constY?.asIntegerValue == null)) return err("matrix size specifiers must be constant integer values") val matrix = heap.get(value.heapId!!).array!! - val expectedSize = constX.asIntegerValue * constY.asIntegerValue + val expectedSize = + if(constY==null) constX.asIntegerValue else constX.asIntegerValue * constY.asIntegerValue!! if (matrix.size != expectedSize) return err("initializer matrix size mismatch (expecting $expectedSize, got ${matrix.size} elements)") + return true } } - return err("invalid matrix initialization value $value") + return err("invalid matrix initialization value of type ${value.type} - expecting byte array") } } return true diff --git a/compiler/src/prog8/compiler/Compiler.kt b/compiler/src/prog8/compiler/Compiler.kt index c651660c2..b183181e9 100644 --- a/compiler/src/prog8/compiler/Compiler.kt +++ b/compiler/src/prog8/compiler/Compiler.kt @@ -1111,6 +1111,7 @@ private class StatementTranslator(private val prog: IntermediateProgram, private fun translate(stmt: VariableInitializationAssignment) { // this is an assignment to initialize a variable's value in the scope. // the compiler can perhaps optimize this phase. + // todo: optimize variable init by keeping track of the block of init values so it can be copied as a whole translate(stmt as Assignment) } diff --git a/compiler/src/prog8/compiler/intermediate/IntermediateProgram.kt b/compiler/src/prog8/compiler/intermediate/IntermediateProgram.kt index 13b388a06..40a077a9a 100644 --- a/compiler/src/prog8/compiler/intermediate/IntermediateProgram.kt +++ b/compiler/src/prog8/compiler/intermediate/IntermediateProgram.kt @@ -8,11 +8,13 @@ import java.io.PrintStream class IntermediateProgram(val name: String, var loadAddress: Int, val heap: HeapValues) { - class ProgramBlock(val scopedname: String, val shortname: String, var address: Int?) { - val instructions = mutableListOf() - val variables = mutableMapOf() - val labels = mutableMapOf() - + class ProgramBlock(val scopedname: String, + val shortname: String, + var address: Int?, + val instructions: MutableList = mutableListOf(), + val variables: MutableMap = mutableMapOf(), + val labels: MutableMap = mutableMapOf()) + { val numVariables: Int get() { return variables.size } val numInstructions: Int diff --git a/compiler/src/prog8/compiler/target/c64/AsmGen.kt b/compiler/src/prog8/compiler/target/c64/AsmGen.kt index 6518e63ad..a681d0d81 100644 --- a/compiler/src/prog8/compiler/target/c64/AsmGen.kt +++ b/compiler/src/prog8/compiler/target/c64/AsmGen.kt @@ -1,32 +1,92 @@ package prog8.compiler.target.c64 +import prog8.ast.DataType import prog8.compiler.* -import prog8.compiler.intermediate.Instruction -import prog8.compiler.intermediate.IntermediateProgram -import prog8.compiler.intermediate.LabelInstr -import prog8.compiler.intermediate.Opcode +import prog8.compiler.intermediate.* import java.io.File +import java.io.PrintWriter import java.util.* +import kotlin.math.abs class AssemblyError(msg: String) : RuntimeException(msg) -class AsmGen(val options: CompilationOptions) { - fun compileToAssembly(program: IntermediateProgram): AssemblyProgram { +class AsmGen(val options: CompilationOptions, val program: IntermediateProgram, val heap: HeapValues) { + private val globalFloatConsts = mutableMapOf() + private lateinit var output: PrintWriter + + init { + // because 64tass understands scoped names via .proc / .block, + // we'll strip the block prefix from all scoped names in the program. + val newblocks = mutableListOf() + for(block in program.blocks) { + val newvars = block.variables.map { symname(it.key, block) to it.value }.toMap().toMutableMap() + val newlabels = block.labels.map { symname(it.key, block) to it.value}.toMap().toMutableMap() + val newinstructions = block.instructions.asSequence().map { + it as? LabelInstr ?: Instruction(it.opcode, it.arg, + if(it.callLabel!=null) symname(it.callLabel, block) else null, + if(it.callLabel2!=null) symname(it.callLabel2, block) else null) + }.toMutableList() + newblocks.add(IntermediateProgram.ProgramBlock( + block.scopedname, + block.shortname, + block.address, + newinstructions, + newvars, + newlabels)) + } + program.blocks.clear() + program.blocks.addAll(newblocks) + + // make a list of all const floats that are used + for(block in program.blocks) { + for(ins in block.instructions.filter{it.arg?.type==DataType.FLOAT}) { + val float = ins.arg!!.numericValue().toDouble() + if(float !in globalFloatConsts) + globalFloatConsts[float] = "prog8_const_float_${globalFloatConsts.size}" + } + } + } + + private fun out(str: String) = output.println(str) + + fun compileToAssembly(): AssemblyProgram { println("\nGenerating assembly code from intermediate code... ") + // todo generate 6502 assembly - val out = File("${program.name}.asm").printWriter() - out.use { - header(out::println, program) + output = File("${program.name}.asm").printWriter() + output.use { + header() for(block in program.blocks) - block2asm(out::println, block) + block2asm(block) } return AssemblyProgram(program.name) } - private fun header(out: (String)->Unit, program: IntermediateProgram) { + private fun symname(scoped: String, block: IntermediateProgram.ProgramBlock) = + if (scoped.startsWith("${block.shortname}.")) scoped.substring(block.shortname.length+1) else scoped + + private fun copyFloat(source: String, target: String) { + // todo: optimize this to bulk copy all floats in the same loop + out("\tldy #4") + out("-\tlda $source,y") + out("\tsta $target,y") + out("\tdey") + out("\tbpl -") + } + + private fun makeFloatFill(flt: Mflpt5): String { + val b0 = "$"+flt.b0.toString(16).padStart(2, '0') + val b1 = "$"+flt.b1.toString(16).padStart(2, '0') + val b2 = "$"+flt.b2.toString(16).padStart(2, '0') + val b3 = "$"+flt.b3.toString(16).padStart(2, '0') + val b4 = "$"+flt.b4.toString(16).padStart(2, '0') + return "$b0, $b1, $b2, $b3, $b4" + } + + private fun header() { val ourName = this.javaClass.name out("; 6502 assembly code for '${program.name}'") out("; generated by $ourName on ${Date()}") @@ -59,66 +119,187 @@ class AsmGen(val options: CompilationOptions) { } } - // call the init methods of each block and then jump to the main.start entrypoint - out("\t; initialize all blocks(reset vars)") - // todo zeropage if it's there - for(block in program.blocks) - out("\tjsr ${block.scopedname}._prog8_init") out("\tlda #0") out("\ttay") out("\ttax") out("\tdex\t; init estack pointer to \$ff") out("\tclc") out("\tjmp main.start\t; jump to program entrypoint") + out("") + + // the global list of all floating point constants for the whole program + for(flt in globalFloatConsts) { + val floatFill = makeFloatFill(Mflpt5.fromNumber(flt.key)) + out("${flt.value}\t.byte $floatFill ; float ${flt.key}") + } } - private fun block2asm(out: (String)->Unit, block: IntermediateProgram.ProgramBlock) { + private fun block2asm(block: IntermediateProgram.ProgramBlock) { out("\n; ---- block: '${block.shortname}' ----") if(block.address!=null) { out(".cerror * > ${block.address?.toHex()}, 'block address overlaps by ', *-${block.address?.toHex()},' bytes'") out("* = ${block.address?.toHex()}") } out("${block.shortname}\t.proc\n") - - // init the variables - out("_prog8_init\t; (re)set vars to initial values") - // todo init vars + out("\n; variables") + vardecls2asm(block) + out("") var skip = 0 for(ins in block.instructions.withIndex()) { if(skip==0) - skip = ins2asm(out, ins.index, ins.value, block) + skip = instr2asm(ins.index, ins.value, block) else skip-- } out("\n\t.pend\n") } + private fun vardecls2asm(block: IntermediateProgram.ProgramBlock) { + val sortedVars = block.variables.toList().sortedBy { it.second.type } + for (v in sortedVars) { + when (v.second.type) { + DataType.UBYTE -> out("${v.first}\t.byte 0") + DataType.BYTE -> out("${v.first}\t.char 0") + DataType.UWORD -> out("${v.first}\t.word 0") + DataType.WORD -> out("${v.first}\t.sint 0") + DataType.FLOAT -> out("${v.first}\t.fill 5 ; float") + DataType.STR, + DataType.STR_P, + DataType.STR_S, + DataType.STR_PS -> { + val rawStr = heap.get(v.second.heapId).str!! + val bytes = encodeStr(rawStr, v.second.type).map { "$" + it.toString(16).padStart(2, '0') } + out("${v.first}\t; ${v.second.type} \"$rawStr\"") + for (chunk in bytes.chunked(16)) + out("\t.byte " + chunk.joinToString()) + } + DataType.ARRAY_UB, DataType.MATRIX_UB -> { + // unsigned integer byte array + val data = makeArrayFillDataUnsigned(v.second) + if (data.size <= 16) + out("${v.first}\t.byte ${data.joinToString()}") + else { + out(v.first) + for (chunk in data.chunked(16)) + out("\t.byte " + chunk.joinToString()) + } + } + DataType.ARRAY_B, DataType.MATRIX_B -> { + // signed integer byte array + val data = makeArrayFillDataSigned(v.second) + if (data.size <= 16) + out("${v.first}\t.char ${data.joinToString()}") + else { + out(v.first) + for (chunk in data.chunked(16)) + out("\t.char " + chunk.joinToString()) + } + } + DataType.ARRAY_UW -> { + // unsigned word array + val data = makeArrayFillDataUnsigned(v.second) + if (data.size <= 16) + out("${v.first}\t.word ${data.joinToString()}") + else { + out(v.first) + for (chunk in data.chunked(16)) + out("\t.word " + chunk.joinToString()) + } + } + DataType.ARRAY_W -> { + // signed word array + val data = makeArrayFillDataSigned(v.second) + if (data.size <= 16) + out("${v.first}\t.sint ${data.joinToString()}") + else { + out(v.first) + for (chunk in data.chunked(16)) + out("\t.sint " + chunk.joinToString()) + } + } + DataType.ARRAY_F -> { + // float array + val array = heap.get(v.second.heapId).doubleArray!! + val floatFills = array.map { makeFloatFill(Mflpt5.fromNumber(it)) } + out(v.first) + for(f in array.zip(floatFills)) + out("\t.byte ${f.second} ; float ${f.first}") + } + } + } + } + + private fun encodeStr(str: String, dt: DataType): List { + when(dt) { + DataType.STR -> { + val bytes = Petscii.encodePetscii(str, true) + return bytes.plus(0) + } + DataType.STR_P -> { + val result = listOf(str.length.toShort()) + val bytes = Petscii.encodePetscii(str, true) + return result.plus(bytes) + } + DataType.STR_S -> { + val bytes = Petscii.encodeScreencode(str, true) + return bytes.plus(0) + } + DataType.STR_PS -> { + val result = listOf(str.length.toShort()) + val bytes = Petscii.encodeScreencode(str, true) + return result.plus(bytes) + } + else -> throw AssemblyError("invalid str type") + } + } + + private fun makeArrayFillDataUnsigned(value: Value): List { + val array = heap.get(value.heapId).array!! + return if (value.type == DataType.ARRAY_UB || value.type == DataType.ARRAY_UW || value.type == DataType.MATRIX_UB) + array.map { "$"+it.toString(16).padStart(2, '0') } + else + throw AssemblyError("invalid array type") + } + + private fun makeArrayFillDataSigned(value: Value): List { + val array = heap.get(value.heapId).array!! + return if (value.type == DataType.ARRAY_B || value.type == DataType.ARRAY_W || value.type == DataType.MATRIX_B) { + array.map { + if(it>=0) + "$"+it.toString(16).padStart(2, '0') + else + "-$"+abs(it).toString(16).padStart(2, '0') + } + } + else throw AssemblyError("invalid array type") + } + private val registerStrings = setOf("A", "X", "Y", "AX", "AY", "XY") // note: to put stuff on the stack, we use Absolute,X addressing mode which is 3 bytes / 4 cycles // possible space optimization is to use zeropage (indirect),Y which is 2 bytes, but 5 cycles - private fun pushByte(byte: Int, out: (String) -> Unit) { + private fun pushByte(byte: Int) { out("\tlda #${byte.toHex()}") out("\tsta ${ESTACK_LO.toHex()},x") out("\tdex") } - private fun pushMemByte(address: Int, out: (String) -> Unit) { + private fun pushMemByte(address: Int) { out("\tlda ${address.toHex()}") out("\tsta ${ESTACK_LO.toHex()},x") out("\tdex") } - private fun pushVarByte(name: String, out: (String) -> Unit) { + private fun pushVarByte(name: String) { out("\tlda $name") out("\tsta ${ESTACK_LO.toHex()},x") out("\tdex") } - private fun pushWord(word: Int, out: (String) -> Unit) { + private fun pushWord(word: Int) { out("\tlda #<${word.toHex()}") out("\tsta ${ESTACK_LO.toHex()},x") out("\tlda #>${word.toHex()}") @@ -126,7 +307,7 @@ class AsmGen(val options: CompilationOptions) { out("\tdex") } - private fun pushMemWord(address: Int, out: (String) -> Unit) { + private fun pushMemWord(address: Int) { out("\tlda ${address.toHex()}") out("\tsta ${ESTACK_LO.toHex()},x") out("\tlda ${(address+1).toHex()}") @@ -134,7 +315,7 @@ class AsmGen(val options: CompilationOptions) { out("\tdex") } - private fun pushVarWord(name: String, out: (String) -> Unit) { + private fun pushVarWord(name: String) { out("\tlda $name") out("\tsta ${ESTACK_LO.toHex()},x") out("\tlda $name+1") @@ -142,18 +323,25 @@ class AsmGen(val options: CompilationOptions) { out("\tdex") } - private fun popByteA(out: (String) -> Unit) { - out("\tinx") - out("\tlda ${ESTACK_LO.toHex()},x") + private fun pushFloat(label: String) { + // todo this is too large for inline, make a subroutine + out("\tlda $label") + out("\tsta ${ESTACK_LO.toHex()},x") + out("\tlda $label+1") + out("\tsta ${ESTACK_HI.toHex()},x") + out("\tdex") + out("\tlda $label+2") + out("\tsta ${ESTACK_LO.toHex()},x") + out("\tlda $label+3") + out("\tsta ${ESTACK_HI.toHex()},x") + out("\tdex") + out("\tlda $label+4") + out("\tsta ${ESTACK_LO.toHex()},x") + // 6th byte is not used for floats + out("\tdex") } - private fun popWordAY(out: (String) -> Unit) { - out("\tinx") - out("\tlda ${ESTACK_LO.toHex()},x") - out("\tldy ${ESTACK_HI.toHex()},x") - } - - private fun ins2asm(out: (String) -> Unit, insIdx: Int, ins: Instruction, block: IntermediateProgram.ProgramBlock): Int { + private fun instr2asm(insIdx: Int, ins: Instruction, block: IntermediateProgram.ProgramBlock): Int { if(ins is LabelInstr) { if(ins.name==block.shortname) return 0 @@ -271,7 +459,7 @@ class AsmGen(val options: CompilationOptions) { return 1 // skip 1 } // byte onto stack - pushByte(ins.arg!!.integerValue(), out) + pushByte(ins.arg!!.integerValue()) } Opcode.PUSH_MEM_UB, Opcode.PUSH_MEM_B -> { val nextIns = block.getIns(insIdx+1) @@ -293,7 +481,7 @@ class AsmGen(val options: CompilationOptions) { return 1 // skip 1 } // byte from memory onto stack - pushMemByte(ins.arg!!.integerValue(), out) + pushMemByte(ins.arg!!.integerValue()) } Opcode.PUSH_VAR_BYTE -> { val nextIns = block.getIns(insIdx+1) @@ -307,7 +495,7 @@ class AsmGen(val options: CompilationOptions) { return 1 // skip 1 } // byte from variable onto stack - pushVarByte(ins.callLabel!!, out) + pushVarByte(ins.callLabel!!) } Opcode.PUSH_WORD -> { val nextIns = block.getIns(insIdx+1) @@ -337,7 +525,7 @@ class AsmGen(val options: CompilationOptions) { out("\tsta ${(nextIns.arg.integerValue()+1).toHex()}") return 1 // skip 1 } - pushWord(ins.arg!!.integerValue(), out) + pushWord(ins.arg!!.integerValue()) } Opcode.PUSH_MEM_UW, Opcode.PUSH_MEM_W -> { val nextIns = block.getIns(insIdx+1) @@ -368,7 +556,7 @@ class AsmGen(val options: CompilationOptions) { return 1 // skip 1 } // word from memory onto stack - pushMemWord(ins.arg!!.integerValue(), out) + pushMemWord(ins.arg!!.integerValue()) } Opcode.PUSH_VAR_WORD -> { val nextIns = block.getIns(insIdx+1) @@ -382,7 +570,7 @@ class AsmGen(val options: CompilationOptions) { return 1 // skip 1 } // word from memory onto stack - pushVarWord(ins.callLabel!!, out) + pushVarWord(ins.callLabel!!) } Opcode.PUSH_FLOAT -> { val nextIns = block.getIns(insIdx+1) @@ -390,7 +578,18 @@ class AsmGen(val options: CompilationOptions) { throw CompilerException("discard after push should have been removed") if(!options.floats) throw CompilerException("floats not enabled") - TODO("push float") + val float = ins.arg!!.numericValue().toDouble() + val label = globalFloatConsts[float]!! + if(nextIns.opcode==Opcode.POP_MEM_FLOAT) { + // set a float in memory to a constant float value + copyFloat(label, nextIns.arg!!.integerValue().toHex()) + return 1 + } else if(nextIns.opcode==Opcode.POP_VAR_FLOAT) { + // set a variable to a constant float value + copyFloat(label, nextIns.callLabel!!) + return 1 + } + pushFloat(label) } Opcode.PUSH_VAR_FLOAT -> { val nextIns = block.getIns(insIdx+1) @@ -400,7 +599,11 @@ class AsmGen(val options: CompilationOptions) { throw CompilerException("push var+pop var should have been replaced by copy var") if(!options.floats) throw CompilerException("floats not enabled") - TODO("push var float") + if(nextIns.opcode==Opcode.POP_MEM_FLOAT) { + copyFloat(ins.callLabel!!, nextIns.arg!!.integerValue().toHex()) // copy var float to memory + return 1 + } + pushFloat(ins.callLabel!!) } Opcode.PUSH_MEM_FLOAT -> { val nextIns = block.getIns(insIdx+1) @@ -408,7 +611,15 @@ class AsmGen(val options: CompilationOptions) { throw CompilerException("discard after push should have been removed") if(!options.floats) throw CompilerException("floats not enabled") - TODO("push mem float") + if(nextIns.opcode==Opcode.POP_VAR_FLOAT) { + copyFloat(ins.arg!!.integerValue().toHex(), nextIns.callLabel!!) // copy memory float to var + return 1 + } + if(nextIns.opcode==Opcode.POP_MEM_FLOAT) { + copyFloat(ins.arg!!.integerValue().toHex(), nextIns.arg!!.integerValue().toHex()) // copy memory float to memory float + return 1 + } + pushFloat(ins.arg!!.integerValue().toHex()) } else-> TODO("asm for $ins") // Opcode.POP_MEM_B -> TODO()