mirror of
https://github.com/irmen/prog8.git
synced 2026-04-19 20:16:51 +00:00
working on vm codegen
This commit is contained in:
@@ -18,7 +18,7 @@ internal class AssemblyProgram(override val name: String,
|
||||
|
||||
override fun assemble(options: CompilationOptions): Boolean {
|
||||
val outfile = options.outputDir / ("$name.p8virt")
|
||||
println("write code to ${outfile}")
|
||||
println("write code to $outfile")
|
||||
outfile.bufferedWriter().use { out ->
|
||||
allocations.asVmMemory().forEach { (name, alloc) ->
|
||||
out.write("; ${name.joinToString(".")}\n")
|
||||
@@ -38,9 +38,18 @@ internal class AssemblyProgram(override val name: String,
|
||||
private fun BufferedWriter.writeLine(line: VmCodeLine) {
|
||||
when(line) {
|
||||
is VmCodeComment -> write("; ${line.comment}\n")
|
||||
is VmCodeInstruction -> write(line.ins.toString() + "\n")
|
||||
is VmCodeInstruction -> {
|
||||
write(line.ins.toString())
|
||||
if(line.labelArg!=null) {
|
||||
if (line.ins.reg1 != null || line.ins.reg2 != null || line.ins.reg3 != null || line.ins.value != null)
|
||||
write(",")
|
||||
else
|
||||
write(" ")
|
||||
write("_" + line.labelArg.joinToString("."))
|
||||
}
|
||||
write("\n")
|
||||
}
|
||||
is VmCodeLabel -> write("_" + line.name.joinToString(".") + ":\n")
|
||||
is VmCodeOpcodeWithStringArg -> write("${line.opcode.name.lowercase()} ${line.arg}\n")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -50,14 +59,18 @@ internal class AssemblyProgram(override val name: String,
|
||||
|
||||
internal sealed class VmCodeLine
|
||||
|
||||
internal class VmCodeInstruction(val ins: Instruction): VmCodeLine()
|
||||
internal class VmCodeInstruction(val ins: Instruction, val labelArg: List<String>?=null): 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 {
|
||||
internal class VmCodeChunk(initial: VmCodeLine? = null) {
|
||||
val lines = mutableListOf<VmCodeLine>()
|
||||
|
||||
init {
|
||||
if(initial!=null)
|
||||
lines.add(initial)
|
||||
}
|
||||
|
||||
operator fun plusAssign(line: VmCodeLine) {
|
||||
lines.add(line)
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ import prog8.code.ast.*
|
||||
import prog8.code.core.*
|
||||
import prog8.vm.Instruction
|
||||
import prog8.vm.Opcode
|
||||
import prog8.vm.DataType
|
||||
import prog8.vm.VmDataType
|
||||
|
||||
|
||||
class CodeGen(internal val program: PtProgram,
|
||||
@@ -16,7 +16,6 @@ class CodeGen(internal val program: PtProgram,
|
||||
|
||||
internal val allocations = VariableAllocator(symbolTable, program, errors)
|
||||
private val expressionEval = ExpressionGen(this)
|
||||
private val instructions = mutableListOf<String>()
|
||||
|
||||
init {
|
||||
if(options.dontReinitGlobals)
|
||||
@@ -24,29 +23,151 @@ class CodeGen(internal val program: PtProgram,
|
||||
}
|
||||
|
||||
override fun compileToAssembly(): IAssemblyProgram? {
|
||||
instructions.clear()
|
||||
val vmprog = AssemblyProgram(program.name, allocations)
|
||||
|
||||
// collect global variables initializers
|
||||
program.allBlocks().forEach {
|
||||
it.children
|
||||
.singleOrNull { node->node is PtScopeVarsInit }
|
||||
?.let { inits ->
|
||||
vmprog.addGlobalInits(translate(inits as PtScopeVarsInit))
|
||||
it.children.remove(inits)
|
||||
}
|
||||
val chunk = VmCodeChunk()
|
||||
it.children.filterIsInstance<PtAssignment>().forEach { assign -> chunk += translate(assign, RegisterUsage(0)) }
|
||||
vmprog.addGlobalInits(chunk)
|
||||
}
|
||||
|
||||
val regUsage = RegisterUsage(0)
|
||||
for (block in program.allBlocks()) {
|
||||
vmprog.addBlock(translate(block))
|
||||
vmprog.addBlock(translate(block, regUsage))
|
||||
}
|
||||
|
||||
return vmprog
|
||||
}
|
||||
|
||||
private fun translate(assignment: PtAssignment): VmCodeChunk {
|
||||
|
||||
private fun translateNode(node: PtNode, regUsage: RegisterUsage): VmCodeChunk {
|
||||
val chunk = when(node) {
|
||||
is PtBlock -> translate(node, regUsage)
|
||||
is PtSub -> translate(node, regUsage)
|
||||
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 PtAssignment -> translate(node, regUsage)
|
||||
is PtNodeGroup -> translateGroup(node.children, regUsage)
|
||||
is PtBuiltinFunctionCall -> expressionEval.translate(node, regUsage.nextFree(), regUsage)
|
||||
is PtFunctionCall -> expressionEval.translate(node, regUsage.nextFree(), regUsage)
|
||||
is PtNop -> VmCodeChunk()
|
||||
is PtReturn -> translate(node)
|
||||
is PtJump -> translate(node)
|
||||
is PtConditionalBranch -> TODO()
|
||||
is PtPipe -> TODO()
|
||||
is PtForLoop -> TODO()
|
||||
is PtIfElse -> TODO()
|
||||
is PtPostIncrDecr -> translate(node, regUsage)
|
||||
is PtRepeatLoop -> translate(node, regUsage)
|
||||
is PtWhen -> TODO()
|
||||
is PtLabel -> VmCodeChunk(VmCodeLabel(node.scopedName))
|
||||
is PtBreakpoint -> VmCodeChunk(VmCodeInstruction(Instruction(Opcode.BREAKPOINT)))
|
||||
is PtAddressOf,
|
||||
is PtContainmentCheck,
|
||||
is PtMemoryByte,
|
||||
is PtProgram,
|
||||
is PtArrayIndexer,
|
||||
is PtBinaryExpression,
|
||||
is PtIdentifier,
|
||||
is PtWhenChoice,
|
||||
is PtPrefix,
|
||||
is PtRange,
|
||||
is PtAssignTarget,
|
||||
is PtTypeCast,
|
||||
is PtSubroutineParameter,
|
||||
is PtNumber,
|
||||
is PtArrayLiteral,
|
||||
is PtString -> throw AssemblyError("$node should not occur as separate statement node")
|
||||
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")
|
||||
}
|
||||
chunk.lines.add(0, VmCodeComment(node.position.toString()))
|
||||
return chunk
|
||||
}
|
||||
|
||||
private fun translate(postIncrDecr: PtPostIncrDecr, regUsage: RegisterUsage): VmCodeChunk {
|
||||
val chunk = VmCodeChunk()
|
||||
val (expressionChunk, resultRegister) = expressionEval.translateExpression(assignment.value)
|
||||
chunk += expressionChunk
|
||||
val operation = when(postIncrDecr.operator) {
|
||||
"++" -> Opcode.INC
|
||||
"--" -> Opcode.DEC
|
||||
else -> throw AssemblyError("weird operator")
|
||||
}
|
||||
val ident = postIncrDecr.target.identifier
|
||||
val memory = postIncrDecr.target.memory
|
||||
val array = postIncrDecr.target.array
|
||||
val vmDt = vmType(postIncrDecr.target.type)
|
||||
val resultReg = regUsage.nextFree()
|
||||
if(ident!=null) {
|
||||
val address = allocations.get(ident.targetName)
|
||||
chunk += VmCodeInstruction(Instruction(Opcode.LOADM, vmDt, reg1=resultReg, value = address))
|
||||
chunk += VmCodeInstruction(Instruction(operation, vmDt, reg1=resultReg))
|
||||
chunk += VmCodeInstruction(Instruction(Opcode.STOREM, vmDt, reg1=resultReg, value = address))
|
||||
} else if(memory!=null) {
|
||||
val addressReg = regUsage.nextFree()
|
||||
chunk += expressionEval.translateExpression(memory.address, addressReg, regUsage)
|
||||
chunk += VmCodeInstruction(Instruction(Opcode.LOADI, vmDt, reg1=resultReg, reg2=addressReg))
|
||||
chunk += VmCodeInstruction(Instruction(operation, vmDt, reg1=resultReg))
|
||||
chunk += VmCodeInstruction(Instruction(Opcode.STOREI, vmDt, reg1=resultReg, reg2=addressReg))
|
||||
} else if (array!=null) {
|
||||
TODO("postincrdecr array")
|
||||
} else
|
||||
throw AssemblyError("weird assigntarget")
|
||||
|
||||
return chunk
|
||||
}
|
||||
|
||||
private fun translate(repeat: PtRepeatLoop, regUsage: RegisterUsage): VmCodeChunk {
|
||||
if((repeat.count as? PtNumber)?.number==0.0)
|
||||
return VmCodeChunk()
|
||||
if((repeat.count as? PtNumber)?.number==1.0)
|
||||
return translateGroup(repeat.children, regUsage)
|
||||
if((repeat.count as? PtNumber)?.number==256.0) {
|
||||
// 256 iterations can still be done with just a byte counter if you set it to zero as starting value.
|
||||
repeat.children[0] = PtNumber(DataType.UBYTE, 0.0, repeat.count.position)
|
||||
}
|
||||
|
||||
val chunk = VmCodeChunk()
|
||||
val counterReg = regUsage.nextFree()
|
||||
val vmDt = vmType(repeat.count.type)
|
||||
chunk += expressionEval.translateExpression(repeat.count, counterReg, regUsage)
|
||||
val repeatLabel = createLabelName()
|
||||
chunk += VmCodeLabel(repeatLabel)
|
||||
chunk += translateNode(repeat.statements, regUsage)
|
||||
chunk += VmCodeInstruction(Instruction(Opcode.DEC, vmDt, reg1=counterReg))
|
||||
chunk += VmCodeInstruction(Instruction(Opcode.BNZ, vmDt, reg1=counterReg), labelArg = repeatLabel)
|
||||
return chunk
|
||||
}
|
||||
|
||||
private fun translate(jump: PtJump): VmCodeChunk {
|
||||
val chunk = VmCodeChunk()
|
||||
if(jump.address!=null)
|
||||
throw AssemblyError("cannot jump to memory location in the vm target")
|
||||
chunk += if(jump.generatedLabel!=null)
|
||||
VmCodeInstruction(Instruction(Opcode.JUMP), labelArg = listOf(jump.generatedLabel!!))
|
||||
else if(jump.identifier!=null)
|
||||
VmCodeInstruction(Instruction(Opcode.JUMP), labelArg = jump.identifier!!.targetName)
|
||||
else
|
||||
throw AssemblyError("weird jump")
|
||||
return chunk
|
||||
}
|
||||
|
||||
private fun translateGroup(group: List<PtNode>, regUsage: RegisterUsage): VmCodeChunk {
|
||||
val chunk = VmCodeChunk()
|
||||
group.forEach { chunk += translateNode(it, regUsage) }
|
||||
return chunk
|
||||
}
|
||||
|
||||
private fun translate(assignment: PtAssignment, regUsage: RegisterUsage): VmCodeChunk {
|
||||
// TODO optimize in-place assignments (assignment.augmentable = true)
|
||||
|
||||
val chunk = VmCodeChunk()
|
||||
val resultRegister = regUsage.nextFree()
|
||||
chunk += expressionEval.translateExpression(assignment.value, resultRegister, regUsage)
|
||||
val ident = assignment.target.identifier
|
||||
val memory = assignment.target.memory
|
||||
val array = assignment.target.array
|
||||
@@ -64,9 +185,8 @@ class CodeGen(internal val program: PtProgram,
|
||||
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
|
||||
val addressRegister = regUsage.nextFree()
|
||||
chunk += expressionEval.translateExpression(assignment.value, addressRegister, regUsage)
|
||||
Instruction(Opcode.STOREI, vmDt, reg1=resultRegister, reg2=addressRegister)
|
||||
}
|
||||
chunk += VmCodeInstruction(ins)
|
||||
@@ -76,124 +196,55 @@ class CodeGen(internal val program: PtProgram,
|
||||
return chunk
|
||||
}
|
||||
|
||||
private fun translateNode(node: PtNode): VmCodeChunk {
|
||||
return when(node) {
|
||||
is PtBlock -> translate(node)
|
||||
is PtSub -> translate(node)
|
||||
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 PtConditionalBranch -> TODO()
|
||||
is PtAddressOf -> TODO()
|
||||
is PtArrayIndexer -> TODO()
|
||||
is PtArrayLiteral -> TODO()
|
||||
is PtBinaryExpression -> TODO()
|
||||
is PtBuiltinFunctionCall -> expressionEval.translate(node)
|
||||
is PtContainmentCheck -> TODO()
|
||||
is PtFunctionCall -> translate(node)
|
||||
is PtIdentifier -> TODO()
|
||||
is PtMemoryByte -> TODO()
|
||||
is PtNumber -> TODO()
|
||||
is PtPipe -> TODO()
|
||||
is PtPrefix -> TODO()
|
||||
is PtRange -> TODO()
|
||||
is PtString -> TODO()
|
||||
is PtTypeCast -> TODO()
|
||||
is PtForLoop -> TODO()
|
||||
is PtIfElse -> TODO()
|
||||
is PtJump -> TODO()
|
||||
is PtNodeGroup -> TODO()
|
||||
is PtNop -> VmCodeChunk()
|
||||
is PtPostIncrDecr -> TODO()
|
||||
is PtProgram -> TODO()
|
||||
is PtRepeatLoop -> 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(fcall: PtFunctionCall): VmCodeChunk {
|
||||
val chunk = VmCodeChunk()
|
||||
for ((index, arg) in fcall.args.withIndex()) {
|
||||
// TODO make sure the expressions code doesn't clobber the previous registers, and doesn't use the r0, r1, r2... which are needed to pass the args
|
||||
val (expressionChunk, resultRegister) = expressionEval.translateExpression(arg)
|
||||
chunk += expressionChunk
|
||||
if(resultRegister!=index)
|
||||
chunk += VmCodeInstruction(Instruction(Opcode.LOADR, vmType(arg.type), reg1=0, reg2=resultRegister))
|
||||
}
|
||||
chunk += VmCodeOpcodeWithStringArg(Opcode.GOSUB, gosubArg(fcall.functionName))
|
||||
return chunk
|
||||
}
|
||||
|
||||
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))
|
||||
// Call Convention: return value is always returned in r0
|
||||
chunk += expressionEval.translateExpression(value, 0, RegisterUsage(1))
|
||||
}
|
||||
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 {
|
||||
private fun translate(sub: PtSub, regUsage: RegisterUsage): VmCodeChunk {
|
||||
// TODO actually inline subroutines marked as inline
|
||||
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)
|
||||
chunk += translateNode(inits)
|
||||
}
|
||||
|
||||
for (child in sub.children) {
|
||||
chunk += translateNode(child)
|
||||
chunk += translateNode(child, regUsage)
|
||||
}
|
||||
chunk += VmCodeComment("SUB-END '${sub.name}'")
|
||||
return chunk
|
||||
}
|
||||
|
||||
private fun translate(block: PtBlock): VmCodeChunk {
|
||||
private fun translate(block: PtBlock, regUsage: RegisterUsage): VmCodeChunk {
|
||||
val chunk = VmCodeChunk()
|
||||
chunk += VmCodeComment("BLOCK '${block.name}' addr=${block.address} lib=${block.library}")
|
||||
for (child in block.children)
|
||||
chunk += translateNode(child)
|
||||
for (child in block.children) {
|
||||
if(child !is PtAssignment) // global variable initialization is done elsewhere
|
||||
chunk += translateNode(child, regUsage)
|
||||
}
|
||||
chunk += VmCodeComment("BLOCK-END '${block.name}'")
|
||||
return chunk
|
||||
}
|
||||
|
||||
|
||||
internal fun gosubArg(targetName: List<String>) = "_${targetName.joinToString(".")}"
|
||||
internal fun vmType(type: DataType): VmDataType {
|
||||
return when(type) {
|
||||
DataType.UBYTE,
|
||||
DataType.BYTE -> VmDataType.BYTE
|
||||
DataType.UWORD,
|
||||
DataType.WORD -> VmDataType.WORD
|
||||
in PassByReferenceDatatypes -> VmDataType.WORD
|
||||
else -> throw AssemblyError("no vm datatype for $type")
|
||||
}
|
||||
}
|
||||
|
||||
private var labelSequenceNumber = 0
|
||||
internal fun createLabelName(): List<String> {
|
||||
labelSequenceNumber++
|
||||
return listOf("generated$labelSequenceNumber")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,22 +1,30 @@
|
||||
package prog8.codegen.virtual
|
||||
|
||||
import prog8.code.StSub
|
||||
import prog8.code.ast.*
|
||||
import prog8.code.core.AssemblyError
|
||||
import prog8.code.core.DataType
|
||||
import prog8.code.core.PassByValueDatatypes
|
||||
import prog8.vm.Instruction
|
||||
import prog8.vm.Opcode
|
||||
import prog8.vm.VmDataType
|
||||
|
||||
|
||||
internal class RegisterUsage(var firstFree: Int=0) {
|
||||
fun nextFree(): Int {
|
||||
val result = firstFree
|
||||
firstFree++
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
internal class ExpressionGen(val codeGen: CodeGen) {
|
||||
fun translateExpression(expr: PtExpression): Pair<VmCodeChunk, Int> {
|
||||
// TODO("Not yet implemented")
|
||||
fun translateExpression(expr: PtExpression, resultRegister: Int, regUsage: RegisterUsage): VmCodeChunk {
|
||||
require(regUsage.firstFree > resultRegister)
|
||||
|
||||
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 -> {
|
||||
@@ -24,65 +32,214 @@ internal class ExpressionGen(val codeGen: CodeGen) {
|
||||
}
|
||||
is PtIdentifier -> {
|
||||
val mem = codeGen.allocations.get(expr.targetName)
|
||||
chunk += VmCodeInstruction(Instruction(Opcode.LOADM, vmDt, reg1=resultRegister, value=mem))
|
||||
chunk += if(expr.type in PassByValueDatatypes) {
|
||||
VmCodeInstruction(Instruction(Opcode.LOADM, vmDt, reg1=resultRegister, value=mem))
|
||||
} else {
|
||||
// for strings and arrays etc., load the *address* of the value instead
|
||||
VmCodeInstruction(Instruction(Opcode.LOAD, 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)
|
||||
val addressRegister = regUsage.nextFree()
|
||||
val addressExprCode = translateExpression(expr.address, addressRegister, regUsage)
|
||||
chunk += addressExprCode
|
||||
}
|
||||
is PtTypeCast -> TODO()
|
||||
is PtPrefix -> TODO()
|
||||
is PtArrayIndexer -> TODO()
|
||||
is PtBinaryExpression -> {
|
||||
val (exprCode, functionResultReg) = translate(expr)
|
||||
process(exprCode, functionResultReg)
|
||||
}
|
||||
is PtBuiltinFunctionCall -> TODO()
|
||||
is PtTypeCast -> chunk += translate(expr, resultRegister, regUsage)
|
||||
is PtPrefix -> chunk += translate(expr, resultRegister, regUsage)
|
||||
is PtArrayIndexer -> chunk += translate(expr, resultRegister, regUsage)
|
||||
is PtBinaryExpression -> chunk += translate(expr, resultRegister, regUsage)
|
||||
is PtBuiltinFunctionCall -> chunk += translate(expr, resultRegister, regUsage)
|
||||
is PtFunctionCall -> chunk += translate(expr, resultRegister, regUsage)
|
||||
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()
|
||||
is PtRange,
|
||||
is PtArrayLiteral,
|
||||
is PtString -> throw AssemblyError("range/arrayliteral/string should no longer occur as expression")
|
||||
else -> throw AssemblyError("weird expression")
|
||||
}
|
||||
return Pair(chunk, resultRegister)
|
||||
return chunk
|
||||
}
|
||||
|
||||
private fun translate(binExpr: PtBinaryExpression): Pair<VmCodeChunk, Int> {
|
||||
private fun translate(arrayIx: PtArrayIndexer, resultRegister: Int, regUsage: RegisterUsage): VmCodeChunk {
|
||||
val eltSize = codeGen.program.memsizer.memorySize(arrayIx.type)
|
||||
val vmDt = codeGen.vmType(arrayIx.type)
|
||||
val chunk = VmCodeChunk()
|
||||
val (leftCode, leftResultReg) = translateExpression(binExpr.left)
|
||||
val (rightCode, rightResultReg) = translateExpression(binExpr.right)
|
||||
val idxReg = regUsage.nextFree()
|
||||
chunk += translateExpression(arrayIx.index, idxReg, regUsage)
|
||||
if(eltSize>1) {
|
||||
val factorReg = regUsage.nextFree()
|
||||
chunk += VmCodeInstruction(Instruction(Opcode.LOAD, VmDataType.BYTE, reg1=factorReg, value=eltSize))
|
||||
chunk += VmCodeInstruction(Instruction(Opcode.MUL, VmDataType.BYTE, reg1=idxReg, reg2=factorReg))
|
||||
}
|
||||
val arrayLocation = codeGen.allocations.get(arrayIx.variable.targetName)
|
||||
chunk += VmCodeInstruction(Instruction(Opcode.LOADX, vmDt, reg1=resultRegister, reg2=idxReg, value = arrayLocation))
|
||||
return chunk
|
||||
}
|
||||
|
||||
private fun translate(expr: PtPrefix, resultRegister: Int, regUsage: RegisterUsage): VmCodeChunk {
|
||||
// operator can be: +, -, ~, not
|
||||
val chunk = VmCodeChunk()
|
||||
chunk += translateExpression(expr.value, resultRegister, regUsage)
|
||||
val vmDt = codeGen.vmType(expr.type)
|
||||
when(expr.operator) {
|
||||
"+" -> { }
|
||||
"-" -> {
|
||||
chunk += VmCodeInstruction(Instruction(Opcode.NEG, vmDt, reg1=resultRegister))
|
||||
}
|
||||
"~" -> {
|
||||
val regMask = regUsage.nextFree()
|
||||
val mask = if(vmDt==VmDataType.BYTE) 0x00ff else 0xffff
|
||||
chunk += VmCodeInstruction(Instruction(Opcode.LOAD, vmDt, reg1=regMask, value=mask))
|
||||
chunk += VmCodeInstruction(Instruction(Opcode.XOR, vmDt, reg1=resultRegister, reg2=resultRegister, reg3=regMask))
|
||||
}
|
||||
"not" -> {
|
||||
val label = codeGen.createLabelName()
|
||||
chunk += VmCodeInstruction(Instruction(Opcode.BZ, vmDt, reg1=resultRegister), labelArg = label)
|
||||
chunk += VmCodeInstruction(Instruction(Opcode.LOAD, vmDt, reg1=resultRegister, value=1))
|
||||
chunk += VmCodeLabel(label)
|
||||
val regMask = regUsage.nextFree()
|
||||
chunk += VmCodeInstruction(Instruction(Opcode.LOAD, vmDt, reg1=regMask, value=1))
|
||||
chunk += VmCodeInstruction(Instruction(Opcode.XOR, vmDt, reg1=resultRegister, reg2=resultRegister, reg3=regMask))
|
||||
}
|
||||
else -> throw AssemblyError("weird prefix operator")
|
||||
}
|
||||
return chunk
|
||||
}
|
||||
|
||||
private fun translate(cast: PtTypeCast, resultRegister: Int, regUsage: RegisterUsage): VmCodeChunk {
|
||||
val chunk = VmCodeChunk()
|
||||
if(cast.type==cast.value.type)
|
||||
return chunk
|
||||
chunk += translateExpression(cast.value, resultRegister, regUsage)
|
||||
when(cast.type) {
|
||||
DataType.UBYTE -> {
|
||||
when(cast.value.type) {
|
||||
DataType.BYTE, DataType.UWORD, DataType.WORD -> { /* just keep the LSB as it is */ }
|
||||
DataType.FLOAT -> {
|
||||
TODO("float -> ubyte") // float not yet supported
|
||||
}
|
||||
else -> throw AssemblyError("weird cast value type")
|
||||
}
|
||||
}
|
||||
DataType.BYTE -> {
|
||||
when(cast.value.type) {
|
||||
DataType.UBYTE, DataType.UWORD, DataType.WORD -> { /* just keep the LSB as it is */ }
|
||||
DataType.FLOAT -> {
|
||||
TODO("float -> byte") // float not yet supported
|
||||
}
|
||||
else -> throw AssemblyError("weird cast value type")
|
||||
}
|
||||
}
|
||||
DataType.UWORD -> {
|
||||
when(cast.value.type) {
|
||||
DataType.BYTE -> {
|
||||
// byte -> uword: sign extend
|
||||
chunk += VmCodeInstruction(Instruction(Opcode.EXTS, type = VmDataType.BYTE, reg1 = resultRegister))
|
||||
}
|
||||
DataType.UBYTE -> {
|
||||
// ubyte -> uword: sign extend
|
||||
chunk += VmCodeInstruction(Instruction(Opcode.EXT, type = VmDataType.BYTE, reg1 = resultRegister))
|
||||
}
|
||||
DataType.WORD -> { }
|
||||
DataType.FLOAT -> {
|
||||
TODO("float -> uword") // float not yet supported
|
||||
}
|
||||
else -> throw AssemblyError("weird cast value type")
|
||||
}
|
||||
}
|
||||
DataType.WORD -> {
|
||||
when(cast.value.type) {
|
||||
DataType.BYTE -> {
|
||||
// byte -> word: sign extend
|
||||
chunk += VmCodeInstruction(Instruction(Opcode.EXTS, type = VmDataType.BYTE, reg1 = resultRegister))
|
||||
}
|
||||
DataType.UBYTE -> {
|
||||
// byte -> word: sign extend
|
||||
chunk += VmCodeInstruction(Instruction(Opcode.EXT, type = VmDataType.BYTE, reg1 = resultRegister))
|
||||
}
|
||||
DataType.UWORD -> { }
|
||||
DataType.FLOAT -> {
|
||||
TODO("float -> word") // float not yet supported
|
||||
}
|
||||
else -> throw AssemblyError("weird cast value type")
|
||||
}
|
||||
}
|
||||
DataType.FLOAT -> {
|
||||
TODO("floating point not yet supported")
|
||||
when(cast.value.type) {
|
||||
DataType.BYTE -> {
|
||||
// TODO("byte -> float")
|
||||
}
|
||||
DataType.UBYTE -> {
|
||||
// TODO("ubyte -> float")
|
||||
}
|
||||
DataType.WORD -> {
|
||||
// TODO("word -> float")
|
||||
}
|
||||
DataType.UWORD -> {
|
||||
// TODO("uword -> float")
|
||||
}
|
||||
else -> throw AssemblyError("weird cast value type")
|
||||
}
|
||||
}
|
||||
else -> throw AssemblyError("weird cast type")
|
||||
}
|
||||
return chunk
|
||||
}
|
||||
|
||||
private fun translate(binExpr: PtBinaryExpression, resultRegister: Int, regUsage: RegisterUsage): VmCodeChunk {
|
||||
val chunk = VmCodeChunk()
|
||||
val leftResultReg = regUsage.nextFree()
|
||||
val rightResultReg = regUsage.nextFree()
|
||||
val leftCode = translateExpression(binExpr.left, leftResultReg, regUsage)
|
||||
val rightCode = translateExpression(binExpr.right, rightResultReg, regUsage)
|
||||
chunk += leftCode
|
||||
chunk += rightCode
|
||||
val resultRegister = 0 // TODO binexpr result can't always be in r0...
|
||||
val vmDt = codeGen.vmType(binExpr.type)
|
||||
when(binExpr.operator) {
|
||||
"+" -> {
|
||||
chunk += VmCodeInstruction(Instruction(Opcode.ADD, codeGen.vmType(binExpr.type), reg1=resultRegister, reg2=leftResultReg, reg3=rightResultReg))
|
||||
chunk += VmCodeInstruction(Instruction(Opcode.ADD, vmDt, reg1=resultRegister, reg2=leftResultReg, reg3=rightResultReg))
|
||||
}
|
||||
"-" -> {
|
||||
chunk += VmCodeInstruction(Instruction(Opcode.SUB, vmDt, reg1=resultRegister, reg2=leftResultReg, reg3=rightResultReg))
|
||||
}
|
||||
"*" -> {
|
||||
chunk += VmCodeInstruction(Instruction(Opcode.MUL, vmDt, reg1=resultRegister, reg2=leftResultReg, reg3=rightResultReg))
|
||||
}
|
||||
"/" -> {
|
||||
chunk += VmCodeInstruction(Instruction(Opcode.DIV, vmDt, reg1=resultRegister, reg2=leftResultReg, reg3=rightResultReg))
|
||||
}
|
||||
"%" -> {
|
||||
chunk += VmCodeInstruction(Instruction(Opcode.MOD, vmDt, reg1=resultRegister, reg2=leftResultReg, reg3=rightResultReg))
|
||||
}
|
||||
else -> TODO("operator ${binExpr.operator}")
|
||||
}
|
||||
return Pair(chunk, resultRegister)
|
||||
return chunk
|
||||
}
|
||||
|
||||
private fun translate(fcall: PtFunctionCall): Pair<VmCodeChunk, Int> {
|
||||
require(!fcall.void)
|
||||
fun translate(fcall: PtFunctionCall, resultRegister: Int, regUsage: RegisterUsage): VmCodeChunk {
|
||||
val subroutine = codeGen.symbolTable.flat.getValue(fcall.functionName) as StSub
|
||||
val chunk = VmCodeChunk()
|
||||
// TODO evaluate arguments
|
||||
chunk += VmCodeOpcodeWithStringArg(Opcode.GOSUB, codeGen.gosubArg(fcall.functionName))
|
||||
return Pair(chunk, 0) // TODO function result always in r0?
|
||||
for ((arg, parameter) in fcall.args.zip(subroutine.parameters)) {
|
||||
val argReg = regUsage.nextFree()
|
||||
chunk += translateExpression(arg, argReg, regUsage)
|
||||
val vmDt = codeGen.vmType(parameter.type)
|
||||
val mem = codeGen.allocations.get(fcall.functionName + parameter.name)
|
||||
chunk += VmCodeInstruction(Instruction(Opcode.STOREM, vmDt, reg1=argReg, value=mem))
|
||||
}
|
||||
chunk += VmCodeInstruction(Instruction(Opcode.CALL), labelArg=fcall.functionName)
|
||||
if(!fcall.void && resultRegister!=0) {
|
||||
// Call convention: result value is in r0, so put it in the required register instead.
|
||||
chunk += VmCodeInstruction(Instruction(Opcode.LOADR, codeGen.vmType(fcall.type), reg1=resultRegister, reg2=0))
|
||||
}
|
||||
return chunk
|
||||
}
|
||||
|
||||
|
||||
|
||||
fun translate(call: PtBuiltinFunctionCall): VmCodeChunk {
|
||||
fun translate(call: PtBuiltinFunctionCall, resultRegister: Int, regUsage: RegisterUsage): VmCodeChunk {
|
||||
val chunk = VmCodeChunk()
|
||||
when(call.name) {
|
||||
"syscall" -> {
|
||||
@@ -90,34 +247,40 @@ internal class ExpressionGen(val codeGen: CodeGen) {
|
||||
chunk += VmCodeInstruction(Instruction(Opcode.SYSCALL, value=vExpr.number.toInt()))
|
||||
}
|
||||
"syscall1" -> {
|
||||
val vExpr = call.args[0] as PtNumber
|
||||
// TODO make sure it evaluates into r0
|
||||
val (expressionChunk0, resultRegister0) = translateExpression(call.args[1])
|
||||
chunk += expressionChunk0
|
||||
chunk += VmCodeInstruction(Instruction(Opcode.SYSCALL, value=vExpr.number.toInt()))
|
||||
chunk += VmCodeInstruction(Instruction(Opcode.PUSH, VmDataType.BYTE, reg1 = 0))
|
||||
val callNr = (call.args[0] as PtNumber).number.toInt()
|
||||
chunk += translateExpression(call.args[1], 0, regUsage)
|
||||
chunk += VmCodeInstruction(Instruction(Opcode.SYSCALL, value=callNr))
|
||||
chunk += VmCodeInstruction(Instruction(Opcode.POP, VmDataType.BYTE, reg1 = 0))
|
||||
}
|
||||
"syscall2" -> {
|
||||
val vExpr = call.args[0] as PtNumber
|
||||
// TODO make sure it evaluates into r0
|
||||
val (expressionChunk0, resultRegister0) = translateExpression(call.args[1])
|
||||
chunk += expressionChunk0
|
||||
// TODO make sure it evaluates into r1
|
||||
val (expressionChunk1, resultRegister1) = translateExpression(call.args[2])
|
||||
chunk += expressionChunk1
|
||||
chunk += VmCodeInstruction(Instruction(Opcode.SYSCALL, value=vExpr.number.toInt()))
|
||||
chunk += VmCodeInstruction(Instruction(Opcode.PUSH, VmDataType.BYTE, reg1 = 0))
|
||||
chunk += VmCodeInstruction(Instruction(Opcode.PUSH, VmDataType.WORD, reg1 = 1))
|
||||
while(regUsage.firstFree<2) {
|
||||
regUsage.nextFree()
|
||||
}
|
||||
val callNr = (call.args[0] as PtNumber).number.toInt()
|
||||
chunk += translateExpression(call.args[1], 0, regUsage)
|
||||
chunk += translateExpression(call.args[2], 1, regUsage)
|
||||
chunk += VmCodeInstruction(Instruction(Opcode.SYSCALL, value=callNr))
|
||||
chunk += VmCodeInstruction(Instruction(Opcode.POP, VmDataType.WORD, reg1 = 1))
|
||||
chunk += VmCodeInstruction(Instruction(Opcode.POP, VmDataType.BYTE, reg1 = 0))
|
||||
}
|
||||
"syscall3" -> {
|
||||
val vExpr = call.args[0] as PtNumber
|
||||
// TODO make sure it evaluates into r0
|
||||
val (expressionChunk0, resultRegister0) = translateExpression(call.args[1])
|
||||
chunk += expressionChunk0
|
||||
// TODO make sure it evaluates into r1
|
||||
val (expressionChunk1, resultRegister1) = translateExpression(call.args[2])
|
||||
chunk += expressionChunk1
|
||||
// TODO make sure it evaluates into r2
|
||||
val (expressionChunk2, resultRegister2) = translateExpression(call.args[3])
|
||||
chunk += expressionChunk2
|
||||
chunk += VmCodeInstruction(Instruction(Opcode.SYSCALL, value=vExpr.number.toInt()))
|
||||
chunk += VmCodeInstruction(Instruction(Opcode.PUSH, VmDataType.BYTE, reg1 = 0))
|
||||
chunk += VmCodeInstruction(Instruction(Opcode.PUSH, VmDataType.WORD, reg1 = 1))
|
||||
chunk += VmCodeInstruction(Instruction(Opcode.PUSH, VmDataType.WORD, reg1 = 2))
|
||||
while(regUsage.firstFree<3) {
|
||||
regUsage.nextFree()
|
||||
}
|
||||
val callNr = (call.args[0] as PtNumber).number.toInt()
|
||||
chunk += translateExpression(call.args[1], 0, regUsage)
|
||||
chunk += translateExpression(call.args[2], 1, regUsage)
|
||||
chunk += translateExpression(call.args[3], 2, regUsage)
|
||||
chunk += VmCodeInstruction(Instruction(Opcode.SYSCALL, value=callNr))
|
||||
chunk += VmCodeInstruction(Instruction(Opcode.POP, VmDataType.WORD, reg1 = 2))
|
||||
chunk += VmCodeInstruction(Instruction(Opcode.POP, VmDataType.WORD, reg1 = 1))
|
||||
chunk += VmCodeInstruction(Instruction(Opcode.POP, VmDataType.BYTE, reg1 = 0))
|
||||
}
|
||||
else -> {
|
||||
TODO("builtinfunc ${call.name}")
|
||||
|
||||
Reference in New Issue
Block a user