This commit is contained in:
Irmen de Jong 2018-10-16 01:24:40 +02:00
parent 85f6c5350c
commit 263b197fec
6 changed files with 303 additions and 84 deletions

View File

@ -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
}
}

View File

@ -106,7 +106,7 @@ fun main(args: Array<String>) {
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()

View File

@ -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

View File

@ -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)
}

View File

@ -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<Instruction>()
val variables = mutableMapOf<String, Value>()
val labels = mutableMapOf<String, Instruction>()
class ProgramBlock(val scopedname: String,
val shortname: String,
var address: Int?,
val instructions: MutableList<Instruction> = mutableListOf(),
val variables: MutableMap<String, Value> = mutableMapOf(),
val labels: MutableMap<String, Instruction> = mutableMapOf())
{
val numVariables: Int
get() { return variables.size }
val numInstructions: Int

View File

@ -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<Double, String>()
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<IntermediateProgram.ProgramBlock>()
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<Short> {
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<String> {
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<String> {
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()