working on vm translator

This commit is contained in:
Irmen de Jong
2022-03-22 01:41:23 +01:00
parent fd581ffc37
commit 06b38506d1
10 changed files with 326 additions and 152 deletions

View File

@@ -2,22 +2,67 @@ package prog8.codegen.virtual
import prog8.code.core.CompilationOptions
import prog8.code.core.IAssemblyProgram
import prog8.vm.Instruction
import prog8.vm.Opcode
import java.io.BufferedWriter
import kotlin.io.path.bufferedWriter
import kotlin.io.path.div
internal class AssemblyProgram(override val name: String,
private val allocations: VariableAllocator,
private val instructions: MutableList<String>
private val allocations: VariableAllocator
) : IAssemblyProgram {
private val globalInits = mutableListOf<VmCodeLine>()
private val blocks = mutableListOf<VmCodeChunk>()
override fun assemble(options: CompilationOptions): Boolean {
val outfile = options.outputDir / ("$name.p8virt")
println("write code to ${outfile}")
outfile.bufferedWriter().use {
allocations.asVmMemory().forEach { alloc -> it.write(alloc + "\n") }
it.write("------PROGRAM------\n")
instructions.forEach { ins -> it.write(ins + "\n") }
outfile.bufferedWriter().use { out ->
allocations.asVmMemory().forEach { (name, alloc) ->
out.write("; ${name.joinToString(".")}\n")
out.write(alloc + "\n")
}
out.write("------PROGRAM------\n")
out.write("; global var inits\n")
globalInits.forEach { out.writeLine(it) }
out.write("; actual program code\n")
blocks.asSequence().flatMap { it.lines }.forEach { line->out.writeLine(line) }
}
return true
}
private fun BufferedWriter.writeLine(line: VmCodeLine) {
when(line) {
is VmCodeComment -> write("; ${line.comment}\n")
is VmCodeInstruction -> write(line.ins.toString() + "\n")
is VmCodeLabel -> write("_" + line.name.joinToString(".") + ":\n")
is VmCodeOpcodeWithStringArg -> write("${line.opcode.name.lowercase()} ${line.arg}\n")
}
}
fun addGlobalInits(chunk: VmCodeChunk) = globalInits.addAll(chunk.lines)
fun addBlock(block: VmCodeChunk) = blocks.add(block)
}
internal sealed class VmCodeLine
internal class VmCodeInstruction(val ins: Instruction): VmCodeLine()
internal class VmCodeLabel(val name: List<String>): VmCodeLine()
internal class VmCodeComment(val comment: String): VmCodeLine()
internal class VmCodeOpcodeWithStringArg(val opcode: Opcode, val arg: String): VmCodeLine()
internal class VmCodeChunk {
val lines = mutableListOf<VmCodeLine>()
operator fun plusAssign(line: VmCodeLine) {
lines.add(line)
}
operator fun plusAssign(chunk: VmCodeChunk) {
lines.addAll(chunk.lines)
}
}

View File

@@ -0,0 +1,43 @@
package prog8.codegen.virtual
import prog8.code.ast.PtBuiltinFunctionCall
import prog8.code.ast.PtNumber
import prog8.vm.Instruction
import prog8.vm.Opcode
internal class BuiltinFunctionsGen(val codegen: CodeGen) {
fun translate(call: PtBuiltinFunctionCall): VmCodeChunk {
val chunk = VmCodeChunk()
when(call.name) {
"syscall" -> {
val vExpr = call.args.single() as PtNumber
chunk += VmCodeInstruction(Instruction(Opcode.SYSCALL, value=vExpr.number.toInt()))
}
"syscall1" -> {
val vExpr = call.args[0] as PtNumber
val vExpr1 = call.args[1] as PtNumber
// TODO assign regs
chunk += VmCodeInstruction(Instruction(Opcode.SYSCALL, value=vExpr.number.toInt()))
}
"syscall2" -> {
val vExpr = call.args[0] as PtNumber
val vExpr1 = call.args[1] as PtNumber
val vExpr2 = call.args[2] as PtNumber
// TODO assign regs
chunk += VmCodeInstruction(Instruction(Opcode.SYSCALL, value=vExpr.number.toInt()))
}
"syscall3" -> {
val vExpr = call.args[0] as PtNumber
val vExpr1 = call.args[1] as PtNumber
val vExpr2 = call.args[2] as PtNumber
val vExpr3 = call.args[3] as PtNumber
// TODO assign regs
chunk += VmCodeInstruction(Instruction(Opcode.SYSCALL, value=vExpr.number.toInt()))
}
else -> {
TODO("builtinfunc ${call.name}")
}
}
return chunk
}
}

View File

@@ -5,6 +5,8 @@ import prog8.code.ast.*
import prog8.code.core.*
import prog8.vm.Instruction
import prog8.vm.Opcode
import prog8.vm.DataType
class CodeGen(internal val program: PtProgram,
internal val symbolTable: SymbolTable,
@@ -12,6 +14,9 @@ class CodeGen(internal val program: PtProgram,
internal val errors: IErrorReporter
): IAssemblyGenerator {
internal val allocations = VariableAllocator(symbolTable, program, errors)
private val builtinFunctions = BuiltinFunctionsGen(this)
private val expressionEval = ExpressionGen(this, builtinFunctions)
private val instructions = mutableListOf<String>()
init {
@@ -21,54 +26,76 @@ class CodeGen(internal val program: PtProgram,
override fun compileToAssembly(): IAssemblyProgram? {
instructions.clear()
val allocations = VariableAllocator(symbolTable, program, errors)
val vmprog = AssemblyProgram(program.name, allocations)
outComment("GLOBAL VARS INITS")
program.allBlocks().forEach {
it.children
.singleOrNull { node->node is PtScopeVarsInit }
?.let { inits->
translateNode(inits)
?.let { inits ->
vmprog.addGlobalInits(translate(inits as PtScopeVarsInit))
it.children.remove(inits)
}
}
outComment("PROGRAM CODE")
for (block in program.allBlocks()) {
translateNode(block)
vmprog.addBlock(translate(block))
}
return AssemblyProgram(program.name, allocations, instructions)
return vmprog
}
private fun out(ins: Instruction) {
instructions.add(ins.toString())
private fun translate(assignment: PtAssignment): VmCodeChunk {
val chunk = VmCodeChunk()
val (expressionChunk, resultRegister) = expressionEval.translateExpression(assignment.value)
chunk += expressionChunk
val ident = assignment.target.identifier
val memory = assignment.target.memory
val array = assignment.target.array
val vmDt = vmType(assignment.value.type)
if(ident!=null) {
val address = allocations.get(ident.targetName)
val ins = Instruction(Opcode.STOREM, vmDt, reg1=resultRegister, value=address)
chunk += VmCodeInstruction(ins)
}
else if(array!=null) {
TODO("assign to array")
}
else if(memory!=null) {
val ins =
if(memory.address is PtNumber) {
Instruction(Opcode.STOREM, vmDt, reg1=resultRegister, value=(memory.address as PtNumber).number.toInt())
} else {
// TODO make sure the registers used in this eval don't overlap with the one above
val (addrExpressionChunk, addressRegister) = expressionEval.translateExpression(assignment.value)
chunk += addrExpressionChunk
Instruction(Opcode.STOREI, vmDt, reg1=resultRegister, reg2=addressRegister)
}
chunk += VmCodeInstruction(ins)
}
else
throw AssemblyError("weird assigntarget")
return chunk
}
private fun outComment(ins: String) {
instructions.add("; $ins")
}
private fun translateNode(node: PtNode) {
when(node) {
private fun translateNode(node: PtNode): VmCodeChunk {
return when(node) {
is PtBlock -> translate(node)
is PtSub -> translate(node)
is PtScopeVarsDecls -> { /* vars should be looked up via symbol table */ }
is PtVariable -> { /* var should be looked up via symbol table */ }
is PtMemMapped -> { /* memmapped var should be looked up via symbol table */ }
is PtConstant -> { /* constants have all been folded into the code */ }
is PtScopeVarsDecls -> VmCodeChunk() // vars should be looked up via symbol table
is PtVariable -> VmCodeChunk() // var should be looked up via symbol table
is PtMemMapped -> VmCodeChunk() // memmapped var should be looked up via symbol table
is PtConstant -> VmCodeChunk() // constants have all been folded into the code
is PtAssignTarget -> TODO()
is PtAssignment -> translate(node)
is PtScopeVarsInit -> translate(node)
is PtBreakpoint -> translate(node)
is PtConditionalBranch -> TODO()
is PtAddressOf -> TODO()
is PtArrayIndexer -> TODO()
is PtArrayLiteral -> TODO()
is PtBinaryExpression -> TODO()
is PtBuiltinFunctionCall -> TODO()
is PtBuiltinFunctionCall -> builtinFunctions.translate(node)
is PtContainmentCheck -> TODO()
is PtFunctionCall -> TODO()
is PtFunctionCall -> translate(node)
is PtIdentifier -> TODO()
is PtMemoryByte -> TODO()
is PtNumber -> TODO()
@@ -78,55 +105,103 @@ class CodeGen(internal val program: PtProgram,
is PtString -> TODO()
is PtTypeCast -> TODO()
is PtForLoop -> TODO()
is PtGosub -> TODO()
is PtGosub -> translate(node)
is PtIfElse -> TODO()
is PtIncludeBinary -> TODO()
is PtJump -> TODO()
is PtNodeGroup -> TODO()
is PtNop -> { }
is PtNop -> VmCodeChunk()
is PtPostIncrDecr -> TODO()
is PtProgram -> TODO()
is PtRepeatLoop -> TODO()
is PtReturn -> TODO()
is PtReturn -> translate(node)
is PtSubroutineParameter -> TODO()
is PtWhen -> TODO()
is PtWhenChoice -> TODO()
is PtLabel -> TODO()
is PtBreakpoint -> TODO()
is PtAsmSub -> throw AssemblyError("asmsub not supported on virtual machine target")
is PtInlineAssembly -> throw AssemblyError("inline assembly not supported on virtual machine target")
is PtIncludeBinary -> throw AssemblyError("inline binary data not supported on virtual machine target")
else -> TODO("missing codegen for $node")
}
}
private fun translate(breakpoint: PtBreakpoint) {
out(Instruction(Opcode.BREAKPOINT))
private fun translate(fcall: PtFunctionCall): VmCodeChunk {
val chunk = VmCodeChunk()
// TODO evaluate function call arguments
chunk += VmCodeOpcodeWithStringArg(Opcode.GOSUB, gosubArg(fcall.functionName))
return chunk
}
private fun translate(init: PtScopeVarsInit) {
init.children.forEach { translateNode(it) }
private fun translate(gosub: PtGosub): VmCodeChunk {
val chunk = VmCodeChunk()
if(gosub.address!=null)
throw AssemblyError("cannot gosub to a memory address in the vm target")
else if(gosub.identifier!=null) {
chunk += VmCodeOpcodeWithStringArg(Opcode.GOSUB, gosubArg(gosub.identifier!!.targetName))
} else if(gosub.generatedLabel!=null) {
chunk += VmCodeOpcodeWithStringArg(Opcode.GOSUB, gosubArg(listOf(gosub.generatedLabel!!)))
}
return chunk
}
private fun translate(sub: PtSub) {
outComment("SUB: ${sub.scopedName} -> ${sub.returntype}")
private fun translate(ret: PtReturn): VmCodeChunk {
val chunk = VmCodeChunk()
val value = ret.value
if(value!=null) {
val (expressionChunk, resultRegister) = expressionEval.translateExpression(value)
chunk += expressionChunk
if(resultRegister!=0)
chunk += VmCodeInstruction(Instruction(Opcode.LOADR, vmType(value.type), reg1=0, reg2=resultRegister))
}
chunk += VmCodeInstruction(Instruction(Opcode.RETURN))
return chunk
}
internal fun vmType(type: prog8.code.core.DataType): DataType {
return when(type) {
prog8.code.core.DataType.UBYTE,
prog8.code.core.DataType.BYTE -> DataType.BYTE
prog8.code.core.DataType.UWORD,
prog8.code.core.DataType.WORD -> DataType.WORD
in PassByReferenceDatatypes -> DataType.WORD
else -> throw AssemblyError("no vm datatype for $type")
}
}
private fun translate(init: PtScopeVarsInit): VmCodeChunk {
val chunk = VmCodeChunk()
init.children.forEach { chunk += translateNode(it) }
return chunk
}
private fun translate(sub: PtSub): VmCodeChunk {
val chunk = VmCodeChunk()
chunk += VmCodeComment("SUB: ${sub.scopedName} -> ${sub.returntype}")
chunk += VmCodeLabel(sub.scopedName)
sub.children
.singleOrNull { it is PtScopeVarsInit }
?.let { inits ->
sub.children.remove(inits)
translateNode(inits)
chunk += translateNode(inits)
}
// TODO rest
outComment("SUB-END ${sub.scopedName}\n")
}
private fun translate(assign: PtAssignment) {
outComment("ASSIGN: ${assign.target.identifier?.targetName} = ${assign.value}")
}
private fun translate(block: PtBlock) {
outComment("BLOCK '${block.name}' addr=${block.address} lib=${block.library}")
for (child in block.children) {
translateNode(child)
for (child in sub.children) {
chunk += translateNode(child)
}
outComment("BLOCK-END '${block.name}'\n")
chunk += VmCodeComment("SUB-END '${sub.name}'")
return chunk
}
private fun translate(block: PtBlock): VmCodeChunk {
val chunk = VmCodeChunk()
chunk += VmCodeComment("BLOCK '${block.name}' addr=${block.address} lib=${block.library}")
for (child in block.children)
chunk += translateNode(child)
chunk += VmCodeComment("BLOCK-END '${block.name}'")
return chunk
}
internal fun gosubArg(targetName: List<String>) = "_${targetName.joinToString(".")}"
}

View File

@@ -0,0 +1,83 @@
package prog8.codegen.virtual
import prog8.code.ast.*
import prog8.code.core.AssemblyError
import prog8.vm.Instruction
import prog8.vm.Opcode
internal class ExpressionGen(val codeGen: CodeGen, val builtinFunctions: BuiltinFunctionsGen) {
fun translateExpression(expr: PtExpression): Pair<VmCodeChunk, Int> {
// TODO("Not yet implemented")
val chunk = VmCodeChunk()
val vmDt = codeGen.vmType(expr.type)
val resultRegister = 0 // TODO need a way to make this dynamic to avoid clobbering existing registers
fun process(code: VmCodeChunk, actualResultReg: Int) {
chunk += code
if(actualResultReg!=resultRegister)
chunk += VmCodeInstruction(Instruction(Opcode.LOADR, vmDt, reg1=resultRegister, reg2=actualResultReg))
}
when (expr) {
is PtNumber -> {
chunk += VmCodeInstruction(Instruction(Opcode.LOAD, vmDt, reg1=resultRegister, value=expr.number.toInt()))
}
is PtIdentifier -> {
val mem = codeGen.allocations.get(expr.targetName)
chunk += VmCodeInstruction(Instruction(Opcode.LOADM, vmDt, reg1=resultRegister, value=mem))
}
is PtAddressOf -> {
val mem = codeGen.allocations.get(expr.identifier.targetName)
chunk += VmCodeInstruction(Instruction(Opcode.LOAD, vmDt, reg1=resultRegister, value=mem))
}
is PtMemoryByte -> {
val (addressExprCode, addressRegister) = translateExpression(expr.address)
process(addressExprCode, addressRegister)
}
is PtTypeCast -> TODO()
is PtPrefix -> TODO()
is PtArrayIndexer -> TODO()
is PtBinaryExpression -> {
val (exprCode, functionResultReg) = translate(expr)
process(exprCode, functionResultReg)
}
is PtBuiltinFunctionCall -> TODO()
is PtContainmentCheck -> TODO()
is PtFunctionCall -> {
val (callCode, functionResultReg) = translate(expr)
process(callCode, functionResultReg)
}
is PtPipe -> TODO()
is PtRange -> TODO()
is PtArrayLiteral -> TODO()
is PtString -> TODO()
else -> throw AssemblyError("weird expression")
}
return Pair(chunk, resultRegister)
}
private fun translate(binExpr: PtBinaryExpression): Pair<VmCodeChunk, Int> {
val chunk = VmCodeChunk()
val (leftCode, leftResultReg) = translateExpression(binExpr.left)
val (rightCode, rightResultReg) = translateExpression(binExpr.right)
chunk += leftCode
chunk += rightCode
val resultRegister = 0 // TODO binexpr result can't always be in r0...
when(binExpr.operator) {
"+" -> {
chunk += VmCodeInstruction(Instruction(Opcode.ADD, codeGen.vmType(binExpr.type), reg1=resultRegister, reg2=leftResultReg, reg3=rightResultReg))
}
else -> TODO("operator ${binExpr.operator}")
}
return Pair(chunk, resultRegister)
}
private fun translate(fcall: PtFunctionCall): Pair<VmCodeChunk, Int> {
require(!fcall.void)
val chunk = VmCodeChunk()
// TODO evaluate arguments
chunk += VmCodeOpcodeWithStringArg(Opcode.GOSUB, codeGen.gosubArg(fcall.functionName))
return Pair(chunk, 0) // TODO function result always in r0?
}
}

View File

@@ -27,13 +27,10 @@ class VariableAllocator(private val st: SymbolTable, private val program: PtProg
freeStart = nextLocation
}
fun asVmMemory(): List<String> {
/*
$4000 strz "Hello from program! "derp" bye.\n"
$2000 ubyte 65,66,67,68,0
$2100 uword $1111,$2222,$3333,$4444
*/
val mm = mutableListOf<String>()
fun get(name: List<String>) = allocations.getValue(name)
fun asVmMemory(): List<Pair<List<String>, String>> {
val mm = mutableListOf<Pair<List<String>, String>>()
for (variable in st.allVariables) {
val location = allocations.getValue(variable.scopedName)
val typeStr = when(variable.dt) {
@@ -67,7 +64,7 @@ $2100 uword $1111,$2222,$3333,$4444
}
else -> throw InternalCompilerException("weird dt")
}
mm.add("${location.toHex()} $typeStr $value")
mm.add(Pair(variable.scopedName, "${location.toHex()} $typeStr $value"))
}
return mm
}