intermediate program written in blocks

This commit is contained in:
Irmen de Jong 2018-10-13 16:09:10 +02:00
parent 987915a77a
commit dff4518608
8 changed files with 576 additions and 525 deletions

View File

@ -31,3 +31,11 @@ sub start() {
}
}
~ block2 $c000 {
str derp="hello"
byte v =44
return
}

View File

@ -99,7 +99,7 @@ fun main(args: Array<String>) {
val stackVmFilename = intermediate.name + "_stackvm.txt"
val stackvmFile = PrintStream(File(stackVmFilename), "utf-8")
intermediate.writeAsText(stackvmFile)
intermediate.writeCode(stackvmFile)
stackvmFile.close()
println("StackVM program code written to '$stackVmFilename'")

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,323 @@
package prog8.compiler
import prog8.ast.DataType
import prog8.ast.LiteralValue
import prog8.ast.Position
import prog8.ast.VarDecl
import prog8.stackvm.*
import java.io.PrintStream
class IntermediateProgram(val name: String, val heap: HeapValues) {
private class ProgramBlock(val scopedname: String, val address: Int?) {
val instructions = mutableListOf<Instruction>()
val variables = mutableMapOf<String, Value>()
val labels = mutableMapOf<String, Instruction>()
val numVariables: Int
get() { return variables.size }
val numInstructions: Int
get() { return instructions.filter { it.opcode!= Opcode.LINE }.size }
}
private val blocks = mutableListOf<ProgramBlock>()
private val memory = mutableMapOf<Int, List<Value>>()
private lateinit var currentBlock: ProgramBlock
val numVariables: Int
get() = blocks.sumBy { it.numVariables }
val numInstructions: Int
get() = blocks.sumBy { it.numInstructions }
fun optimize() {
println("Optimizing stackVM code...")
optimizeDataConversionAndUselessDiscards()
optimizeVariableCopying()
optimizeMultipleSequentialLineInstrs()
// todo optimize stackvm code more
// remove nops (that are not a label)
for (blk in blocks) {
blk.instructions.removeIf { it.opcode== Opcode.NOP && it !is LabelInstr }
}
}
private fun optimizeMultipleSequentialLineInstrs() {
for(blk in blocks) {
val instructionsToReplace = mutableMapOf<Int, Instruction>()
blk.instructions.asSequence().withIndex().windowed(2).toList().forEach {
if (it[0].value.opcode == Opcode.LINE && it[1].value.opcode == Opcode.LINE)
instructionsToReplace[it[0].index] = Instruction(Opcode.NOP)
}
for (rins in instructionsToReplace) {
blk.instructions[rins.key] = rins.value
}
}
}
private fun optimizeVariableCopying() {
for(blk in blocks) {
val instructionsToReplace = mutableMapOf<Int, Instruction>()
blk.instructions.asSequence().withIndex().windowed(2).toList().forEach {
when (it[0].value.opcode) {
Opcode.PUSH_VAR_BYTE ->
if (it[1].value.opcode == Opcode.POP_VAR_BYTE) {
if (it[0].value.callLabel != it[1].value.callLabel)
instructionsToReplace[it[0].index] = Instruction(Opcode.COPY_VAR_BYTE, null, it[0].value.callLabel, it[1].value.callLabel)
else
instructionsToReplace[it[0].index] = Instruction(Opcode.NOP)
instructionsToReplace[it[1].index] = Instruction(Opcode.NOP)
}
Opcode.PUSH_VAR_WORD ->
if (it[1].value.opcode == Opcode.POP_VAR_WORD) {
if (it[0].value.callLabel != it[1].value.callLabel)
instructionsToReplace[it[0].index] = Instruction(Opcode.COPY_VAR_WORD, null, it[0].value.callLabel, it[1].value.callLabel)
else
instructionsToReplace[it[0].index] = Instruction(Opcode.NOP)
instructionsToReplace[it[1].index] = Instruction(Opcode.NOP)
}
Opcode.PUSH_VAR_FLOAT ->
if (it[1].value.opcode == Opcode.POP_VAR_FLOAT) {
if (it[0].value.callLabel != it[1].value.callLabel)
instructionsToReplace[it[0].index] = Instruction(Opcode.COPY_VAR_FLOAT, null, it[0].value.callLabel, it[1].value.callLabel)
else
instructionsToReplace[it[0].index] = Instruction(Opcode.NOP)
instructionsToReplace[it[1].index] = Instruction(Opcode.NOP)
}
else -> {
}
}
}
for (rins in instructionsToReplace) {
blk.instructions[rins.key] = rins.value
}
}
}
private fun optimizeDataConversionAndUselessDiscards() {
// - push value followed by a data type conversion -> push the value in the correct type and remove the conversion
// - push something followed by a discard -> remove both
val instructionsToReplace = mutableMapOf<Int, Instruction>()
fun optimizeDiscardAfterPush(index0: Int, index1: Int, ins1: Instruction) {
if (ins1.opcode == Opcode.DISCARD_FLOAT || ins1.opcode == Opcode.DISCARD_WORD || ins1.opcode == Opcode.DISCARD_BYTE) {
instructionsToReplace[index0] = Instruction(Opcode.NOP)
instructionsToReplace[index1] = Instruction(Opcode.NOP)
}
}
fun optimizeFloatConversion(index0: Int, index1: Int, ins1: Instruction) {
when (ins1.opcode) {
Opcode.LSB,
Opcode.MSB,
Opcode.B2WORD,
Opcode.UB2UWORD,
Opcode.MSB2WORD,
Opcode.B2FLOAT,
Opcode.UB2FLOAT,
Opcode.UW2FLOAT,
Opcode.W2FLOAT -> throw CompilerException("invalid conversion following a float")
Opcode.DISCARD_FLOAT -> {
instructionsToReplace[index0] = Instruction(Opcode.NOP)
instructionsToReplace[index1] = Instruction(Opcode.NOP)
}
Opcode.DISCARD_BYTE, Opcode.DISCARD_WORD -> throw CompilerException("invalid discard type following a float")
else -> throw CompilerException("invalid conversion opcode ${ins1.opcode}")
}
}
fun optimizeWordConversion(index0: Int, ins0: Instruction, index1: Int, ins1: Instruction) {
when (ins1.opcode) {
Opcode.LSB -> {
val ins = Instruction(Opcode.PUSH_BYTE, Value(DataType.UBYTE, ins0.arg!!.integerValue() and 255))
instructionsToReplace[index0] = ins
instructionsToReplace[index1] = Instruction(Opcode.NOP)
}
Opcode.MSB -> {
val ins = Instruction(Opcode.PUSH_BYTE, Value(DataType.UBYTE, ins0.arg!!.integerValue() ushr 8 and 255))
instructionsToReplace[index0] = ins
instructionsToReplace[index1] = Instruction(Opcode.NOP)
}
Opcode.B2WORD,
Opcode.UB2UWORD,
Opcode.MSB2WORD,
Opcode.B2FLOAT,
Opcode.UB2FLOAT -> throw CompilerException("invalid conversion following a word")
Opcode.W2FLOAT, Opcode.UW2FLOAT -> {
val ins = Instruction(Opcode.PUSH_FLOAT, Value(DataType.FLOAT, ins0.arg!!.integerValue().toDouble()))
instructionsToReplace[index0] = ins
instructionsToReplace[index1] = Instruction(Opcode.NOP)
}
Opcode.DISCARD_WORD -> {
instructionsToReplace[index0] = Instruction(Opcode.NOP)
instructionsToReplace[index1] = Instruction(Opcode.NOP)
}
Opcode.DISCARD_BYTE, Opcode.DISCARD_FLOAT -> throw CompilerException("invalid discard type following a byte")
else -> throw CompilerException("invalid conversion opcode ${ins1.opcode}")
}
}
fun optimizeByteConversion(index0: Int, ins0: Instruction, index1: Int, ins1: Instruction) {
when (ins1.opcode) {
Opcode.LSB -> instructionsToReplace[index1] = Instruction(Opcode.NOP)
Opcode.MSB -> throw CompilerException("msb of a byte")
Opcode.UB2UWORD -> {
val ins = Instruction(Opcode.PUSH_WORD, Value(DataType.UWORD, ins0.arg!!.integerValue()))
instructionsToReplace[index0] = ins
instructionsToReplace[index1] = Instruction(Opcode.NOP)
}
Opcode.B2WORD -> {
val ins = Instruction(Opcode.PUSH_WORD, Value(DataType.WORD, ins0.arg!!.integerValue()))
instructionsToReplace[index0] = ins
instructionsToReplace[index1] = Instruction(Opcode.NOP)
}
Opcode.MSB2WORD -> {
val ins = Instruction(Opcode.PUSH_WORD, Value(DataType.UWORD, 256 * ins0.arg!!.integerValue()))
instructionsToReplace[index0] = ins
instructionsToReplace[index1] = Instruction(Opcode.NOP)
}
Opcode.B2FLOAT, Opcode.UB2FLOAT -> {
val ins = Instruction(Opcode.PUSH_FLOAT, Value(DataType.FLOAT, ins0.arg!!.integerValue().toDouble()))
instructionsToReplace[index0] = ins
instructionsToReplace[index1] = Instruction(Opcode.NOP)
}
Opcode.W2FLOAT, Opcode.UW2FLOAT -> throw CompilerException("invalid conversion following a byte")
Opcode.DISCARD_BYTE -> {
instructionsToReplace[index0] = Instruction(Opcode.NOP)
instructionsToReplace[index1] = Instruction(Opcode.NOP)
}
Opcode.DISCARD_WORD, Opcode.DISCARD_FLOAT -> throw CompilerException("invalid discard type following a byte")
else -> throw CompilerException("invalid conversion opcode ${ins1.opcode}")
}
}
for(blk in blocks) {
instructionsToReplace.clear()
val typeConversionOpcodes = setOf(
Opcode.LSB,
Opcode.MSB,
Opcode.B2WORD,
Opcode.UB2UWORD,
Opcode.MSB2WORD,
Opcode.B2FLOAT,
Opcode.UB2FLOAT,
Opcode.W2FLOAT,
Opcode.UW2FLOAT,
Opcode.DISCARD_BYTE,
Opcode.DISCARD_WORD,
Opcode.DISCARD_FLOAT
)
blk.instructions.asSequence().withIndex().windowed(2).toList().forEach {
if (it[1].value.opcode in typeConversionOpcodes) {
when (it[0].value.opcode) {
Opcode.PUSH_BYTE -> optimizeByteConversion(it[0].index, it[0].value, it[1].index, it[1].value)
Opcode.PUSH_WORD -> optimizeWordConversion(it[0].index, it[0].value, it[1].index, it[1].value)
Opcode.PUSH_FLOAT -> optimizeFloatConversion(it[0].index, it[1].index, it[1].value)
Opcode.PUSH_VAR_FLOAT,
Opcode.PUSH_VAR_WORD,
Opcode.PUSH_VAR_BYTE,
Opcode.PUSH_MEM_B, Opcode.PUSH_MEM_UB,
Opcode.PUSH_MEM_W, Opcode.PUSH_MEM_UW,
Opcode.PUSH_MEM_FLOAT -> optimizeDiscardAfterPush(it[0].index, it[1].index, it[1].value)
else -> {
}
}
}
}
for (rins in instructionsToReplace) {
blk.instructions[rins.key] = rins.value
}
}
}
fun variable(scopedname: String, decl: VarDecl) {
val value = when(decl.datatype) {
DataType.UBYTE, DataType.BYTE, DataType.UWORD, DataType.WORD, DataType.FLOAT -> Value(decl.datatype, (decl.value as LiteralValue).asNumericValue!!)
DataType.STR, DataType.STR_P, DataType.STR_S, DataType.STR_PS -> {
val litval = (decl.value as LiteralValue)
if(litval.heapId==null)
throw CompilerException("string should already be in the heap")
Value(decl.datatype, litval.heapId)
}
DataType.ARRAY_B, DataType.ARRAY_W, DataType.MATRIX_B, DataType.MATRIX_UB,
DataType.ARRAY_UB, DataType.ARRAY_UW, DataType.ARRAY_F -> {
val litval = (decl.value as LiteralValue)
if(litval.heapId==null)
throw CompilerException("array/matrix should already be in the heap")
Value(decl.datatype, litval.heapId)
}
}
currentBlock.variables[scopedname] = value
}
fun instr(opcode: Opcode, arg: Value? = null, callLabel: String? = null) {
currentBlock.instructions.add(Instruction(opcode, arg, callLabel))
}
fun label(labelname: String) {
val instr = LabelInstr(labelname)
currentBlock.instructions.add(instr)
currentBlock.labels[labelname] = instr
}
fun line(position: Position) {
currentBlock.instructions.add(Instruction(Opcode.LINE, callLabel = "${position.line} ${position.file}"))
}
fun newBlock(scopedname: String, address: Int?) {
currentBlock = ProgramBlock(scopedname, address)
blocks.add(currentBlock)
}
fun writeCode(out: PrintStream, embeddedLabels: Boolean=true) {
out.println("; stackVM program code for '$name'")
out.println("%memory")
if(memory.isNotEmpty()) {
TODO("print out initial memory load")
}
out.println("%end_memory")
out.println("%heap")
heap.allStrings().forEach {
out.println("${it.index} ${it.value.type.toString().toLowerCase()} \"${it.value.str}\"")
}
heap.allArrays().forEach {
out.println("${it.index} ${it.value.type.toString().toLowerCase()} ${it.value.array!!.toList()}")
}
heap.allDoubleArrays().forEach {
out.println("${it.index} ${it.value.type.toString().toLowerCase()} ${it.value.doubleArray!!.toList()}")
}
out.println("%end_heap")
for(blk in blocks) {
out.println("\n%block ${blk.scopedname} ${blk.address?.toString(16) ?: ""}")
out.println("%variables")
for(variable in blk.variables) {
val valuestr = variable.value.toString()
out.println("${variable.key} ${variable.value.type.toString().toLowerCase()} $valuestr")
}
out.println("%end_variables")
out.println("%instructions")
val labels = blk.labels.entries.associateBy({it.value}) {it.key}
for(instr in blk.instructions) {
if(!embeddedLabels) {
val label = labels[instr]
if (label != null)
out.println("$label:")
} else {
out.println(instr)
}
}
out.println("%end_instructions")
out.println("%end_block")
}
}
}

View File

@ -4,25 +4,33 @@ import prog8.ast.DataType
import prog8.compiler.HeapValues
import prog8.compiler.unescape
import java.io.File
import java.io.PrintStream
import java.util.*
import java.util.regex.Pattern
class Program (val name: String,
prog: MutableList<Instruction>,
val program: MutableList<Instruction>,
val variables: Map<String, Value>,
val labels: Map<String, Instruction>,
val variables: Map<String, Map<String, Value>>,
val memory: Map<Int, List<Value>>,
val heap: HeapValues)
{
init {
// add end of program marker and some sentinel instructions, to correctly connect all others
program.add(LabelInstr("____program_end"))
program.add(Instruction(Opcode.TERMINATE))
program.add(Instruction(Opcode.NOP))
connect()
}
companion object {
fun load(filename: String): Program {
val lines = File(filename).readLines().withIndex().iterator()
val memory = mutableMapOf<Int, List<Value>>()
val vars = mutableMapOf<String, MutableMap<String, Value>>()
var instructions = mutableListOf<Instruction>()
var labels = mapOf<String, Instruction>()
val heap = HeapValues()
val program = mutableListOf<Instruction>()
val variables = mutableMapOf<String, Value>()
val labels = mutableMapOf<String, Instruction>()
while(lines.hasNext()) {
val (lineNr, line) = lines.next()
if(line.startsWith(';') || line.isEmpty())
@ -31,16 +39,33 @@ class Program (val name: String,
loadMemory(lines, memory)
else if(line=="%heap")
loadHeap(lines, heap)
else if(line=="%variables")
loadVars(lines, vars)
else if(line=="%instructions") {
val (insResult, labelResult) = loadInstructions(lines, heap)
instructions = insResult
labels = labelResult
}
else if(line.startsWith("%block "))
loadBlock(lines, heap, program, variables, labels)
else throw VmExecutionException("syntax error at line ${lineNr + 1}")
}
return Program(filename, instructions, labels, vars, memory, heap)
return Program(filename, program, variables, labels, memory, heap)
}
private fun loadBlock(lines: Iterator<IndexedValue<String>>,
heap: HeapValues,
program: MutableList<Instruction>,
variables: MutableMap<String, Value>,
labels: MutableMap<String, Instruction>)
{
while(true) {
val (lineNr, line) = lines.next()
if(line.isEmpty())
continue
else if(line=="%end_block")
return
else if(line=="%variables")
loadVars(lines, variables)
else if(line=="%instructions") {
val (blockInstructions, blockLabels) = loadInstructions(lines, heap)
program.addAll(blockInstructions)
labels.putAll(blockLabels)
}
}
}
private fun loadHeap(lines: Iterator<IndexedValue<String>>, heap: HeapValues) {
@ -156,7 +181,7 @@ class Program (val name: String,
}
private fun loadVars(lines: Iterator<IndexedValue<String>>,
vars: MutableMap<String, MutableMap<String, Value>>): Map<String, Map<String, Value>> {
vars: MutableMap<String, Value>): Map<String, Value> {
val splitpattern = Pattern.compile("\\s+")
while(true) {
val (lineNr, line) = lines.next()
@ -197,10 +222,7 @@ class Program (val name: String,
}
}
}
val blockname = name.substringBefore('.')
val blockvars = vars[blockname] ?: mutableMapOf()
vars[blockname] = blockvars
blockvars[name] = value
vars[name] = value
}
}
@ -229,15 +251,6 @@ class Program (val name: String,
}
}
val program: List<Instruction>
init {
prog.add(LabelInstr("____program_end"))
prog.add(Instruction(Opcode.TERMINATE))
prog.add(Instruction(Opcode.NOP))
program = prog
connect()
}
private fun connect() {
val it1 = program.iterator()
@ -283,43 +296,4 @@ class Program (val name: String,
}
}
}
fun print(out: PrintStream, embeddedLabels: Boolean=true) {
out.println("; stackVM program code for '$name'")
out.println("%memory")
if(memory.isNotEmpty()) {
TODO("print out initial memory load")
}
out.println("%end_memory")
out.println("%heap")
heap.allStrings().forEach {
out.println("${it.index} ${it.value.type.toString().toLowerCase()} \"${it.value.str}\"")
}
heap.allArrays().forEach {
out.println("${it.index} ${it.value.type.toString().toLowerCase()} ${it.value.array!!.toList()}")
}
heap.allDoubleArrays().forEach {
out.println("${it.index} ${it.value.type.toString().toLowerCase()} ${it.value.doubleArray!!.toList()}")
}
out.println("%end_heap")
out.println("%variables")
// just flatten all block vars into one global list for now...
for(variable in variables.flatMap { e->e.value.entries}) {
val valuestr = variable.value.toString()
out.println("${variable.key} ${variable.value.type.toString().toLowerCase()} $valuestr")
}
out.println("%end_variables")
out.println("%instructions")
val labels = this.labels.entries.associateBy({it.value}) {it.key}
for(instr in this.program) {
if(!embeddedLabels) {
val label = labels[instr]
if (label != null)
out.println("$label:")
} else {
out.println(instr)
}
}
out.println("%end_instructions")
}
}

View File

@ -384,9 +384,7 @@ class StackVm(private var traceOutputFile: String?) {
this.heap = program.heap
this.canvas = canvas
canvas?.requestFocusInWindow()
variables.clear()
for(variable in program.variables.flatMap { e->e.value.entries })
variables[variable.key] = variable.value
variables = program.variables.toMutableMap()
if("A" in variables || "X" in variables || "Y" in variables ||
"XY" in variables || "AX" in variables ||"AY" in variables)

View File

@ -50,18 +50,8 @@ class TestStackVmOpcodes {
vars: Map<String, Value>?=null,
labels: Map<String, Instruction>?=null,
mem: Map<Int, List<Value>>?=null) : Program {
val blockvars = mutableMapOf<String, MutableMap<String, Value>>()
if(vars!=null) {
for (blockvar in vars) {
val blockname = blockvar.key.substringBefore('.')
val variables = blockvars[blockname] ?: mutableMapOf()
blockvars[blockname] = variables
variables[blockvar.key] = blockvar.value
}
}
val heap = HeapValues()
return Program("test", ins, labels ?: mapOf(), blockvars, mem ?: mapOf(), heap)
return Program("test", ins, vars ?: mapOf(), labels ?: mapOf(), mem ?: mapOf(), heap)
}
@Test
@ -252,7 +242,6 @@ class TestStackVmOpcodes {
assertEquals(42.25, vm.mem.getFloat(0x4000))
}
@Test
fun testPopVar() {
val ins = mutableListOf(

View File

@ -25,15 +25,28 @@ class TestCompiler {
assertEquals("10", 10.toHex())
assertEquals("10", 10.99.toHex())
assertEquals("15", 15.toHex())
assertEquals("$10", 16.toHex())
assertEquals("\$10", 16.toHex())
assertEquals("\$ff", 255.toHex())
assertEquals("$0100", 256.toHex())
assertEquals("$4e5c", 20060.toHex())
assertEquals("\$0100", 256.toHex())
assertEquals("\$4e5c", 20060.toHex())
assertEquals("\$c382", 50050.toHex())
assertEquals("\$ffff", 65535.toHex())
assertEquals("\$ffff", 65535L.toHex())
assertEquals("0", 0.toHex())
assertEquals("-1", (-1).toHex())
assertEquals("-1", (-1.234).toHex())
assertEquals("-10", (-10).toHex())
assertEquals("-10", (-10.99).toHex())
assertEquals("-15", (-15).toHex())
assertEquals("-\$10", (-16).toHex())
assertEquals("-\$ff", (-255).toHex())
assertEquals("-\$0100", (-256).toHex())
assertEquals("-\$4e5c", (-20060).toHex())
assertEquals("-\$c382", (-50050).toHex())
assertEquals("-\$ffff", (-65535).toHex())
assertEquals("-\$ffff", (-65535L).toHex())
assertFailsWith<CompilerException> { 65536.toHex() }
assertFailsWith<CompilerException> { (-1).toHex() }
assertFailsWith<CompilerException> { (-1.99).toHex() }
assertFailsWith<CompilerException> { 65536L.toHex() }
}