working on vm codegen

This commit is contained in:
Irmen de Jong 2022-03-23 01:52:01 +01:00
parent dbc7ad2ec4
commit 27f6d47efa
22 changed files with 836 additions and 443 deletions

View File

@ -176,6 +176,7 @@ class StConstant(name: String, val dt: DataType, val value: Double, position: Po
}
}
class StMemVar(name: String, val dt: DataType, val address: UInt, position: Position) :
StNode(name, StNodeType.MEMVAR, position) {
override fun printProperties() {
@ -184,13 +185,23 @@ class StMemVar(name: String, val dt: DataType, val address: UInt, position: Posi
}
class StRomSub(name: String, val address: UInt, position: Position) :
StNode(name, StNodeType.ROMSUB, position) {
class StSub(name: String, val parameters: List<StSubroutineParameter>, position: Position) :
StNode(name, StNodeType.SUBROUTINE, position) {
override fun printProperties() {
print(name)
}
}
class StRomSub(name: String, val address: UInt, parameters: List<StSubroutineParameter>, position: Position) :
StNode(name, StNodeType.ROMSUB, position) {
override fun printProperties() {
print("$name address=${address.toHex()}")
}
}
class StSubroutineParameter(val name: String, val type: DataType)
class StArrayElement(val number: Double?, val addressOf: List<String>?)
typealias StString = Pair<String, Encoding>

View File

@ -116,8 +116,3 @@ class PtNop(position: Position): PtNode(position) {
class PtScopeVarsDecls(position: Position): PtNode(position) {
override fun printProperties() {}
}
class PtScopeVarsInit(position: Position): PtNode(position) {
override fun printProperties() {}
}

View File

@ -58,6 +58,16 @@ class PtAssignTarget(position: Position) : PtNode(position) {
val memory: PtMemoryByte?
get() = children.single() as? PtMemoryByte
val type: DataType
get() {
return when(val tgt = children.single()) {
is PtIdentifier -> tgt.type
is PtArrayIndexer -> tgt.type // TODO array to elt type?
is PtMemoryByte -> tgt.type
else -> throw AssemblyError("weird dt")
}
}
override fun printProperties() {}
}

View File

@ -40,7 +40,7 @@ class VirtualMachineDefinition: IMachineDefinition {
assembler.initializeMemory(memsrc, memory)
val program = assembler.assembleProgram(programsrc)
val vm = VirtualMachine(memory, program)
vm.run()
vm.run(throttle = true)
}
override fun isIOAddress(address: UInt): Boolean = false

View File

@ -668,6 +668,15 @@ internal class ExpressionsAsmGen(private val program: Program,
}
"not" -> {
when(type) {
// if reg==0 ->
/*
lda P8ESTACK_LO+1,x
beq +
lda #1
+ eor #1
sta P8ESTACK_LO+1,x
rts
*/
in ByteDatatypes -> asmgen.out(" jsr prog8_lib.not_byte")
in WordDatatypes -> asmgen.out(" jsr prog8_lib.not_word")
else -> throw AssemblyError("weird type")

View File

@ -186,7 +186,6 @@ class AstToXmlConverter(internal val program: PtProgram,
is PtNop -> {}
is PtBreakpoint -> write(it)
is PtScopeVarsDecls -> write(it)
is PtScopeVarsInit -> write(it)
is PtNodeGroup -> it.children.forEach { writeNode(it) }
else -> TODO("$it")
}
@ -199,13 +198,6 @@ class AstToXmlConverter(internal val program: PtProgram,
xml.endElt()
}
private fun write(inits: PtScopeVarsInit) {
xml.elt("varsinit")
xml.startChildren()
inits.children.forEach { writeNode(it) }
xml.endElt()
}
private fun write(breakPt: PtBreakpoint) {
xml.elt("breakpoint")
xml.pos(breakPt.position)

View File

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

View File

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

View File

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

View File

@ -1,4 +1,4 @@
; Prog8 definitions for the Text I/O and Screen routines for the Virtual Machine
; Prog8 definitions for the Text I/O console routines for the Virtual Machine
;
; Written by Irmen de Jong (irmen@razorvine.net) - license: GNU GPL 3.0
@ -7,10 +7,6 @@
txt {
const ubyte DEFAULT_WIDTH = 40
const ubyte DEFAULT_HEIGHT = 24
sub clear_screen() {
txt.chrout(125)
}
@ -23,85 +19,130 @@ sub spc() {
txt.chrout(' ')
}
sub fill_screen (ubyte char) {
; ---- fill the character screen with the given fill character.
; TODO
}
sub clear_screenchars (ubyte char) {
; ---- clear the character screen with the given fill character (leaves colors)
; (assumes screen matrix is at the default address)
; TODO
}
sub chrout(ubyte char) {
; TODO
syscall1(2, char)
}
sub print (str text) {
; ---- print null terminated string from A/Y
; note: the compiler contains an optimization that will replace
; a call to this subroutine with a string argument of just one char,
; by just one call to CHROUT of that single char.
; TODO
syscall1(3, text)
}
sub print_ub0 (ubyte value) {
; ---- print the ubyte in A in decimal form, with left padding 0s (3 positions total)
; TODO
; ---- print the ubyte in A in decimal form, with left padding 0s (3 positions total)
; TODO use conv module?
ubyte hundreds = value / 100
value -= hundreds*100
ubyte tens = value / 10
value -= tens*10
chrout(hundreds+'0')
chrout(tens+'0')
chrout(value+'0')
}
sub print_ub (ubyte value) {
; ---- print the ubyte in A in decimal form, without left padding 0s
; TODO
; ---- print the ubyte in decimal form, without left padding 0s
; TODO use conv module?
ubyte hundreds = value / 100
value -= hundreds*100
ubyte tens = value / 10
value -= tens*10
if hundreds
goto print_hundreds
if tens
goto print_tens
goto print_ones
print_hundreds:
chrout(hundreds+'0')
print_tens:
chrout(tens+'0')
print_ones:
chrout(value+'0')
}
sub print_b (byte value) {
; ---- print the byte in A in decimal form, without left padding 0s
; TODO
; ---- print the byte in decimal form, without left padding 0s
; TODO use conv module?
}
sub print_ubhex (ubyte value, ubyte prefix) {
; ---- print the ubyte in A in hex form (if Carry is set, a radix prefix '$' is printed as well)
; TODO
; ---- print the ubyte in hex form
; TODO use conv module?
}
sub print_ubbin (ubyte value, ubyte prefix) {
; ---- print the ubyte in A in binary form (if Carry is set, a radix prefix '%' is printed as well)
; TODO
; ---- print the ubyte in binary form
; TODO use conv module?
}
sub print_uwbin (uword value, ubyte prefix) {
; ---- print the uword in A/Y in binary form (if Carry is set, a radix prefix '%' is printed as well)
; TODO
; ---- print the uword in binary form
; TODO use conv module?
}
sub print_uwhex (uword value, ubyte prefix) {
; ---- print the uword in A/Y in hexadecimal form (4 digits)
; (if Carry is set, a radix prefix '$' is printed as well)
; TODO
; ---- print the uword in hexadecimal form (4 digits)
; TODO use conv module?
}
sub print_uw0 (uword value) {
; ---- print the uword in A/Y in decimal form, with left padding 0s (5 positions total)
; TODO
; ---- print the uword value in decimal form, with left padding 0s (5 positions total)
; TODO use conv module?
ubyte tenthousands = value / 10000 as ubyte
value -= tenthousands * 10000
ubyte thousands = value / 1000 as ubyte
value -= thousands * 1000
ubyte hundreds = value / 100 as ubyte
value -= hundreds*100
ubyte tens = value / 10 as ubyte
value -= tens*10
chrout(tenthousands+'0')
chrout(thousands+'0')
chrout(hundreds+'0')
chrout(tens+'0')
chrout(value as ubyte + '0')
}
sub print_uw (uword value) {
; ---- print the uword in A/Y in decimal form, without left padding 0s
; TODO
; ---- print the uword in decimal form, without left padding 0s
ubyte tenthousands = value / 10000 as ubyte
value -= tenthousands * 10000
ubyte thousands = value / 1000 as ubyte
value -= thousands * 1000
ubyte hundreds = value / 100 as ubyte
value -= hundreds*100
ubyte tens = value / 10 as ubyte
value -= tens*10
if tenthousands
goto print_tenthousands
if thousands
goto print_thousands
if hundreds
goto print_hundreds
if tens
goto print_tens
goto print_ones
print_tenthousands:
chrout(tenthousands+'0')
print_thousands:
chrout(thousands+'0')
print_hundreds:
chrout(hundreds+'0')
print_tens:
chrout(tens+'0')
print_ones:
chrout(value as ubyte + '0')
}
sub print_w (word value) {
; ---- print the (signed) word in A/Y in decimal form, without left padding 0's
; TODO
; ---- print the (signed) word in decimal form, without left padding 0's
; TODO use conv module?
}
sub input_chars (uword buffer) -> ubyte {
; ---- Input a string (max. 80 chars) from the keyboard. Returns length in Y. (string is terminated with a 0 byte as well)
; It assumes the keyboard is selected as I/O channel!
; TODO
return 0
; ---- Input a string (max. 80 chars) from the keyboard. Returns length of input. (string is terminated with a 0 byte as well)
; It assumes the keyboard is selected as I/O channel!
; TODO
return 0
}
}

View File

@ -131,14 +131,10 @@ class IntermediateAstMaker(val program: Program) {
private fun transform(srcBlock: Block): PtBlock {
val (vardecls, statements) = srcBlock.statements.partition { it is VarDecl }
val (varinits, actualStatements) = statements.partition { (it as? Assignment)?.origin==AssignmentOrigin.VARINIT }
val block = PtBlock(srcBlock.name, srcBlock.address, srcBlock.isInLibrary, srcBlock.position)
if(vardecls.isNotEmpty()) block.add(makeScopeVarsDecls(vardecls, srcBlock.position))
if(varinits.isNotEmpty()) block.add(makeScopeVarInitializers(varinits, srcBlock.position))
for (stmt in actualStatements)
for (stmt in statements)
block.add(transformStatement(stmt))
return block
}
@ -150,14 +146,6 @@ class IntermediateAstMaker(val program: Program) {
return decls
}
private fun makeScopeVarInitializers(varinits: List<Statement>, position: Position): PtScopeVarsInit {
val init = PtScopeVarsInit(position)
varinits.forEach {
init.add(transformStatement(it as Assignment))
}
return init
}
private fun transform(srcNode: BuiltinFunctionCallStatement): PtBuiltinFunctionCall {
val type = builtinFunctionReturnType(srcNode.name, srcNode.args, program).getOr(DataType.UNDEFINED)
val call = PtBuiltinFunctionCall(srcNode.name, true, type, srcNode.position)
@ -236,23 +224,38 @@ class IntermediateAstMaker(val program: Program) {
val parent = gosub.parent as IStatementContainer
val gosubIdx = parent.statements.indexOf(gosub)
val previousNodes = parent.statements.subList(0, gosubIdx).reversed()
val paramAssigns = mutableListOf<Assignment>()
val paramValues = mutableMapOf<String, Expression>()
for (node in previousNodes) {
if(node !is Assignment || node.origin!=AssignmentOrigin.PARAMETERASSIGN)
break
paramAssigns += node
paramValues[node.target.identifier!!.nameInSource.last()] = node.value
}
// instead of just assigning to the parameters, another way is to use push()/pop()
if(previousNodes.isNotEmpty()) {
val first = previousNodes[0] as? FunctionCallStatement
if(first!=null && first.target.nameInSource == listOf("pop")) {
val numPops = previousNodes.indexOfFirst { (it as? FunctionCallStatement)?.target?.nameInSource != listOf("pop") }
val pops = previousNodes.subList(0, numPops)
val pushes = previousNodes.subList(numPops, numPops+numPops)
// TODO one of these has to be reversed?
for ((push, pop) in pushes.zip(pops)) {
val name = ((pop as FunctionCallStatement).args.single() as IdentifierReference).nameInSource.last()
val arg = (push as FunctionCallStatement).args.single()
paramValues[name] = arg
}
}
}
val parameters = gosub.identifier.targetSubroutine(program)!!.parameters
if(paramAssigns.size != parameters.size)
if(paramValues.size != parameters.size)
throw FatalAstException("mismatched number of parameter assignments for function call")
val target = transform(gosub.identifier)
val call = PtFunctionCall(target.targetName, true, DataType.UNDEFINED, gosub.position)
// put arguments in correct order for the parameters
val namedAssigns = paramAssigns.associate { it.target.identifier!!.targetVarDecl(program)!!.name to it.value }
parameters.forEach {
val argument = namedAssigns.getValue(it.name)
val argument = paramValues.getValue(it.name)
call.add(transformExpression(argument))
}
@ -348,7 +351,6 @@ class IntermediateAstMaker(val program: Program) {
private fun transformSub(srcSub: Subroutine): PtSub {
val (vardecls, statements) = srcSub.statements.partition { it is VarDecl }
val (varinits, actualStatements) = statements.partition { (it as? Assignment)?.origin==AssignmentOrigin.VARINIT }
val sub = PtSub(srcSub.name,
srcSub.parameters.map { PtSubroutineParameter(it.name, it.type, it.position) },
srcSub.returntypes.singleOrNull(),
@ -356,8 +358,7 @@ class IntermediateAstMaker(val program: Program) {
srcSub.position)
if(vardecls.isNotEmpty()) sub.add(makeScopeVarsDecls(vardecls, sub.position))
if(varinits.isNotEmpty()) sub.add(makeScopeVarInitializers(varinits, srcSub.position))
for (statement in actualStatements)
for (statement in statements)
sub.add(transformStatement(statement))
return sub

View File

@ -7,6 +7,7 @@ import prog8.ast.expressions.*
import prog8.ast.statements.*
import prog8.ast.walk.IAstVisitor
import prog8.code.core.*
import prog8.code.target.VMTarget
import prog8.compiler.BuiltinFunctions
import prog8.compiler.builtinFunctionReturnType
import java.io.CharConversionException
@ -288,7 +289,7 @@ internal class AstChecker(private val program: Program,
}
}
if(subroutine.inline && !subroutine.isAsmSubroutine)
if(compilerOptions.compTarget.name!=VMTarget.NAME && subroutine.inline && !subroutine.isAsmSubroutine)
err("subroutine inlining is currently only supported on asmsub routines")
if(subroutine.parent !is Block && subroutine.parent !is Subroutine)
@ -498,8 +499,8 @@ internal class AstChecker(private val program: Program,
if (assignment.value !is FunctionCallExpression)
errors.err("assignment value is invalid or has no proper datatype, maybe forgot '&' (address-of)", assignment.value.position)
} else {
checkAssignmentCompatible(targetDatatype.getOr(DataType.BYTE),
sourceDatatype.getOr(DataType.BYTE), assignment.value)
checkAssignmentCompatible(targetDatatype.getOr(DataType.UNDEFINED),
sourceDatatype.getOr(DataType.UNDEFINED), assignment.value)
}
}
}
@ -1463,8 +1464,15 @@ internal class AstChecker(private val program: Program,
sourceValue: Expression) : Boolean {
val position = sourceValue.position
if(sourceValue is RangeExpression)
if(sourceValue is RangeExpression) {
errors.err("can't assign a range value to something else", position)
return false
}
if(sourceDatatype==DataType.UNDEFINED) {
errors.err("assignment right hand side doesn't result in a value", position)
return false
}
val result = when(targetDatatype) {
DataType.BYTE -> sourceDatatype== DataType.BYTE

View File

@ -7,6 +7,7 @@ import prog8.ast.statements.*
import prog8.ast.walk.AstWalker
import prog8.ast.walk.IAstModification
import prog8.code.core.*
import prog8.code.target.VMTarget
import prog8.codegen.cpu6502.asmsub6502ArgsEvalOrder
import prog8.codegen.cpu6502.asmsub6502ArgsHaveRegisterClobberRisk
import prog8.compiler.BuiltinFunctions
@ -411,7 +412,7 @@ internal class StatementReorderer(val program: Program,
override fun after(functionCallStatement: FunctionCallStatement, parent: Node): Iterable<IAstModification> {
val function = functionCallStatement.target.targetStatement(program)!!
checkUnusedReturnValues(functionCallStatement, function, program, errors)
return tryReplaceCallWithGosub(functionCallStatement, parent, program)
return tryReplaceCallWithGosub(functionCallStatement, parent, program, options)
}
}
@ -419,14 +420,19 @@ internal class StatementReorderer(val program: Program,
internal fun tryReplaceCallWithGosub(
functionCallStatement: FunctionCallStatement,
parent: Node,
program: Program
program: Program,
options: CompilationOptions
): Iterable<IAstModification> {
val callee = functionCallStatement.target.targetStatement(program)!!
if(callee is Subroutine) {
if(callee.inline)
return emptyList()
return if(callee.isAsmSubroutine)
tryReplaceCallAsmSubWithGosub(functionCallStatement, parent, callee)
return if(callee.isAsmSubroutine) {
if(options.compTarget.name==VMTarget.NAME)
emptyList()
else
tryReplaceCallAsmSubWithGosub(functionCallStatement, parent, callee)
}
else
tryReplaceCallNormalSubWithGosub(functionCallStatement, parent, callee, program)
}

View File

@ -35,12 +35,13 @@ internal class SymbolTableMaker: IAstVisitor {
}
override fun visit(subroutine: Subroutine) {
val parameters = subroutine.parameters.map { StSubroutineParameter(it.name, it.type) }
if(subroutine.asmAddress!=null) {
val node = StRomSub(subroutine.name, subroutine.asmAddress!!, subroutine.position)
val node = StRomSub(subroutine.name, subroutine.asmAddress!!, parameters, subroutine.position)
scopestack.peek().add(node)
// st.origAstLinks[subroutine] = node
} else {
val node = StNode(subroutine.name, StNodeType.SUBROUTINE, subroutine.position)
val node = StSub(subroutine.name, parameters, subroutine.position)
scopestack.peek().add(node)
scopestack.push(node)
super.visit(subroutine)

View File

@ -200,7 +200,7 @@ internal class VariousCleanups(val program: Program, val errors: IErrorReporter,
}
override fun after(functionCallStatement: FunctionCallStatement, parent: Node): Iterable<IAstModification> {
return tryReplaceCallWithGosub(functionCallStatement, parent, program)
return tryReplaceCallWithGosub(functionCallStatement, parent, program, options)
}
}

View File

@ -40,8 +40,9 @@ class TestIntermediateAst: FunSpec({
blocks[0].scopedName shouldBe listOf("main")
val vars = entry.children[0] as PtScopeVarsDecls
val inits = entry.children[1] as PtScopeVarsInit
inits.children.size shouldBe 1
val ccInit = entry.children[1] as PtAssignment
ccInit.target.identifier?.targetName shouldBe listOf("main","start","cc")
(ccInit.value as PtNumber).number shouldBe 0.0
val ccdecl = vars.children[0] as PtVariable
ccdecl.name shouldBe "cc"

View File

@ -3,6 +3,8 @@ TODO
For next release
^^^^^^^^^^^^^^^^
- simplify cx16.joystick_get2() once this cx16 rom issue is resolved: https://github.com/commanderx16/x16-rom/issues/203
Can now be resolved because the fix got merged https://github.com/commanderx16/x16-rom/pull/204
...
@ -14,16 +16,12 @@ Need help with
- see the :ref:`portingguide` for details on what information is needed.
Blocked by an official Commander-x16 r39 release
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
- simplify cx16.joystick_get2() once this cx16 rom issue is resolved: https://github.com/commanderx16/x16-rom/issues/203
(I hope this will be included into the r39 roms when they get released)
Future Things and Ideas
^^^^^^^^^^^^^^^^^^^^^^^
Compiler:
- vm code gen: don't reuse registers, don't pre allocate variables (except strings + arrays) but instead put them into registers too
then we have Static Single Assignment form in the VM code
- pipe operator: allow non-unary function calls in the pipe that specify the other argument(s) in the calls.
- writeAssembly(): make it possible to actually get rid of the VarDecl nodes by fixing the rest of the code mentioned there.
- make everything an expression? (get rid of Statements. Statements are expressions with void return types?).

View File

@ -1,16 +1,23 @@
%import textio
main {
ubyte variable = 42
sub start() {
txt.print("value=")
txt.print_ub(variable)
variable = foo()
}
syscall1(8, 0) ; enable lo res creen
ubyte shifter
sub foo() -> ubyte {
return variable+1
repeat {
uword xx
uword yy = 0
repeat 240 {
xx = 0
repeat 320 {
syscall3(10, xx, yy, xx*yy + shifter) ; plot pixel
xx++
}
yy++
}
shifter+=4
}
}
}

View File

@ -72,7 +72,7 @@ class Assembler {
} else {
val (_, instr, typestr, rest) = match.groupValues
val opcode = Opcode.valueOf(instr.uppercase())
var type: DataType? = convertType(typestr)
var type: VmDataType? = convertType(typestr)
val operands = rest.lowercase().split(",").toMutableList()
var reg1: Int? = null
var reg2: Int? = null
@ -113,7 +113,7 @@ class Assembler {
}
val format = instructionFormats.getValue(opcode)
if(type==null && format.datatypes.isNotEmpty())
type= DataType.BYTE
type= VmDataType.BYTE
if(type!=null && type !in format.datatypes)
throw IllegalArgumentException("invalid type code for $line")
if(format.reg1 && reg1==null)
@ -161,11 +161,11 @@ class Assembler {
return value.toInt()
}
private fun convertType(typestr: String): DataType? {
private fun convertType(typestr: String): VmDataType? {
return when(typestr.lowercase()) {
"" -> null
".b" -> DataType.BYTE
".w" -> DataType.WORD
".b" -> VmDataType.BYTE
".w" -> VmDataType.WORD
else -> throw IllegalArgumentException("invalid type $typestr")
}
}

View File

@ -9,7 +9,7 @@ import kotlin.math.max
import kotlin.math.min
class GraphicsWindow(val pixelWidth: Int, val pixelHeight: Int, val pixelScaling: Int): JFrame("vm-graphics $pixelWidth × $pixelHeight"), AutoCloseable {
class GraphicsWindow(val pixelWidth: Int, val pixelHeight: Int, val pixelScaling: Int): JFrame("Prog8 VM Graphics Screen $pixelWidth × $pixelHeight"), AutoCloseable {
private lateinit var repaintTimer: Timer
fun start() {

View File

@ -2,12 +2,11 @@ package prog8.vm
/*
65536 virtual registers, maximum 16 bits wide.
65536 bytes of memory.
So a memory pointer is also limited to 16 bits.
Virtual machine:
TODO: also make a 3-address-code instructionset?
65536 virtual registers, 16 bits wide, can also be used as 8 bits. r0-r65535
65536 bytes of memory. Thus memory pointers (addresses) are limited to 16 bits.
Value stack, max 128 entries.
Instruction serialization format possibility:
@ -18,92 +17,111 @@ REGISTER 1: 2 bytes
REGISTER 2: 2 bytes
REG3/MEMORY/VALUE: 2 bytes
Instructions with Type come in variants b/w/f (omitting it in the instruction means '8' if instruction otherwise has a T)
Currently NO support for 24 or 32 bits, and FLOATING POINT is not implemented yet either.
Instructions with Type come in variants 'b' and 'w' (omitting it in the instruction means 'b' by default)
Currently NO support for 24 or 32 bits, and FLOATING POINT is not implemented yet either. FP would be
a separate set of registers and instructions/routines anyway.
*only* LOAD AND STORE instructions have a possible memory operand, all other instructions use only registers or immediate value.
LOAD/STORE -- all have type b/w
LOAD/STORE
----------
All have type b or w.
load reg1, value - load immediate value into register
loadm reg1, value - load reg1 with value in memory address given by value
loadm reg1, address - load reg1 with value in memory address
loadi reg1, reg2 - load reg1 with value in memory indirect, memory pointed to by reg2
loadx reg1, reg2, value - load reg1 with value in memory address given by value, indexed by value in reg2
loadx reg1, reg2, address - load reg1 with value in memory address, indexed by value in reg2
loadr reg1, reg2 - load reg1 with value in register reg2
swapreg reg1, reg2 - swap values in reg1 and reg2
storem reg1, value - store reg1 in memory address given by value
storem reg1, address - store reg1 in memory address
storei reg1, reg2 - store reg1 in memory indirect, memory pointed to by reg2
storex reg1, reg2, value - store reg1 in memory address given by value, indexed by value in reg2
storez value - store zero in memory given by value
storex reg1, reg2, address - store reg1 in memory address, indexed by value in reg2
storez address - store zero in memory address
storezi reg1 - store zero in memory pointed to by reg
storezx reg1, value - store zero in memory given by value, indexed by value in reg
storezx reg1, address - store zero in memory address, indexed by value in reg
FLOW CONTROL
Subroutine call convention:
Subroutine parameters set in Reg 0, 1, 2... before gosub.
Return value in Reg 0 before return.
CONTROL FLOW
------------
Possible subroutine call convention:
Set parameters in Reg 0, 1, 2... before call. Return value set in Reg 0 before return.
But you can decide whatever you want because here we just care about jumping and returning the flow of control.
Saving/restoring registers is possible with PUSH and POP instructions.
jump location - continue running at instruction number given by location
jumpi reg1 - continue running at instruction number in reg1
gosub location - save current instruction location+1, continue execution at location
gosubi reg1 - gosub to subroutine at instruction number in reg1
call location - save current instruction location+1, continue execution at instruction nr given by location
calli reg1 - save current instruction location+1, continue execution at instruction number in reg1
syscall value - do a systemcall identified by call number
return - restore last saved instruction location and continue at that instruction
branch instructions have b/w/f types (f not implemented)
BRANCHING
---------
All have type b or w.
bz reg1, value - branch if reg1 is zero
bnz reg1, value - branch if reg1 is not zero
beq reg1, reg2, value - jump to location in program given by value, if reg1 == reg2
bne reg1, reg2, value - jump to location in program given by value, if reg1 != reg2
blt reg1, reg2, value - jump to location in program given by value, if reg1 < reg2 (unsigned)
blts reg1, reg2, value - jump to location in program given by value, if reg1 < reg2 (signed)
bgt reg1, reg2, value - jump to location in program given by value, if reg1 > reg2 (unsigned)
bgts reg1, reg2, value - jump to location in program given by value, if reg1 > reg2 (signed)
ble reg1, reg2, value - jump to location in program given by value, if reg1 <= reg2 (unsigned)
bles reg1, reg2, value - jump to location in program given by value, if reg1 <= reg2 (signed)
bgt reg1, reg2, value - jump to location in program given by value, if reg1 > reg2 (unsigned)
bgts reg1, reg2, value - jump to location in program given by value, if reg1 > reg2 (signed)
bge reg1, reg2, value - jump to location in program given by value, if reg1 >= reg2 (unsigned)
bges reg1, reg2, value - jump to location in program given by value, if reg1 >= reg2 (signed)
TODO: support for the prog8 special branching instructions if_XX (bcc, bcs etc.)
but we don't have any 'processor flags' whatsoever in the vm so it's a bit weird
INTEGER ARITHMETIC - all have a type of b/w.
(note: the types of the result and both operands, are all identical UNLESS OTHERWISE NOTED).
neg reg1, reg2 - reg1 = sign negation of reg2
INTEGER ARITHMETIC
------------------
All have type b or w. Note: result types are the same as operand types! E.g. byte*byte->byte.
ext reg1 - reg1 = unsigned extension of reg1 (which in practice just means clearing the MSB / MSW) (latter not yet implemented as we don't have longs yet)
exts reg1 - reg1 = signed extension of reg1 (byte to word, or word to long) (note: latter ext.w, not yet implemented as we don't have longs yet)
inc reg1 - reg1 = reg1+1
dec reg1 - reg1 = reg1-1
neg reg1 - reg1 = sign negation of reg1
add reg1, reg2, reg3 - reg1 = reg2+reg3 (unsigned + signed)
sub reg1, reg2, reg3 - reg1 = reg2-reg3 (unsigned + signed)
ext reg1, reg2 - reg1 = unsigned extension of reg2 (which in practice just means clearing the MSB / MSW) (latter not yet implemented as we don't have longs yet)
exts reg1, reg2 - reg1 = signed extension of reg2 (byte to word, or word to long) (note: latter ext.w, not yet implemented as we don't have longs yet)
mul reg1, reg2, reg3 - unsigned multiply reg1=reg2*reg3 note: byte*byte->byte, no type extension to word!
div reg1, reg2, reg3 - unsigned division reg1=reg2/reg3 note: division by zero yields max signed int $ff/$ffff
mod reg1, reg2, reg3 - remainder (modulo) of unsigned division reg1=reg2%reg3 note: division by zero yields max signed int $ff/$ffff
TODO signed mul/div/mod?
LOGICAL/BITWISE - all have a type of b/w. but never f
inv reg1, reg2 - reg1 = bitwise invert of reg2
LOGICAL/BITWISE
---------------
All have type b or w.
and reg1, reg2, reg3 - reg1 = reg2 bitwise and reg3
or reg1, reg2, reg3 - reg1 = reg2 bitwise or reg3
xor reg1, reg2, reg3 - reg1 = reg2 bitwise xor reg3
lsr reg1, reg2 - reg1 = shift reg2 right by 1 bit
lsl reg1, reg2 - reg1 = shift reg2 left by 1 bit
ror reg1, reg2 - reg1 = rotate reg2 right by 1 bit, not using carry
rol reg1, reg2 - reg1 = rotate reg2 left by 1 bit, not using carry
lsr reg1 - reg1 = shift reg1 right by 1 bit
lsl reg1 - reg1 = shift reg1 left by 1 bit
ror reg1 - reg1 = rotate reg1 right by 1 bit, not using carry
rol reg1 - reg1 = rotate reg1 left by 1 bit, not using carry
Not sure if the variants using the carry bit should be added, for the ror/rol instructions.
These do map directly on 6502 and 68k instructions though.
TODO also add ror/rol variants using the carry bit? These do map directly on 6502 and 68k instructions.
MISC
----
nop - do nothing
breakpoint - trigger a breakpoint
copy reg1, reg2, length - copy memory from ptrs in reg1 to reg3, length bytes
copyz reg1, reg2 - copy memory from ptrs in reg1 to reg3, stop after first 0-byte
swap [b, w] reg1, reg2 - reg1 = swapped lsb and msb from register reg2 (16 bits) or lsw and msw (32 bits)
push [b, w] reg1 - push value in reg1 on the stack
pop [b, w] reg1 - pop value from stack into reg1
*/
@ -124,8 +142,8 @@ enum class Opcode {
JUMP,
JUMPI,
GOSUB,
GOSUBI,
CALL,
CALLI,
SYSCALL,
RETURN,
BZ,
@ -141,15 +159,17 @@ enum class Opcode {
BGE,
BGES,
INC,
DEC,
NEG,
ADD,
SUB,
MUL,
DIV,
MOD,
EXT,
EXTS,
INV,
AND,
OR,
XOR,
@ -158,21 +178,23 @@ enum class Opcode {
ROR,
ROL,
PUSH,
POP,
SWAP,
COPY,
COPYZ,
SWAP,
BREAKPOINT
}
enum class DataType {
enum class VmDataType {
BYTE,
WORD
// TODO add INT (32-bit)?
// TODO add INT (32-bit)? INT24 (24-bit)?
}
data class Instruction(
val opcode: Opcode,
val type: DataType?=null,
val type: VmDataType?=null,
val reg1: Int?=null, // 0-$ffff
val reg2: Int?=null, // 0-$ffff
val reg3: Int?=null, // 0-$ffff
@ -180,9 +202,12 @@ data class Instruction(
) {
override fun toString(): String {
val result = mutableListOf(opcode.name.lowercase())
if(instructionFormats.getValue(opcode).datatypes.isNotEmpty() && type==null)
throw IllegalArgumentException("opcode $opcode requires type")
when(type) {
DataType.BYTE -> result.add(".b ")
DataType.WORD -> result.add(".w ")
VmDataType.BYTE -> result.add(".b ")
VmDataType.WORD -> result.add(".w ")
else -> result.add(" ")
}
reg1?.let {
@ -206,14 +231,13 @@ data class Instruction(
}
}
data class InstructionFormat(val datatypes: Set<DataType>, val reg1: Boolean, val reg2: Boolean, val reg3: Boolean, val value: Boolean)
data class InstructionFormat(val datatypes: Set<VmDataType>, val reg1: Boolean, val reg2: Boolean, val reg3: Boolean, val value: Boolean)
private val NN = emptySet<DataType>()
private val BW = setOf(DataType.BYTE, DataType.WORD)
private val NN = emptySet<VmDataType>()
private val BW = setOf(VmDataType.BYTE, VmDataType.WORD)
@Suppress("BooleanLiteralArgument")
val instructionFormats = mutableMapOf(
// opcode to types, reg1, reg2, reg3, value
Opcode.NOP to InstructionFormat(NN, false, false, false, false),
Opcode.LOAD to InstructionFormat(BW, true, false, false, true ),
Opcode.LOADM to InstructionFormat(BW, true, false, false, true ),
@ -230,8 +254,8 @@ val instructionFormats = mutableMapOf(
Opcode.JUMP to InstructionFormat(NN, false, false, false, true ),
Opcode.JUMPI to InstructionFormat(NN, true, false, false, false),
Opcode.GOSUB to InstructionFormat(NN, false, false, false, true ),
Opcode.GOSUBI to InstructionFormat(NN, true, false, false, false),
Opcode.CALL to InstructionFormat(NN, false, false, false, true ),
Opcode.CALLI to InstructionFormat(NN, true, false, false, false),
Opcode.SYSCALL to InstructionFormat(NN, false, false, false, true ),
Opcode.RETURN to InstructionFormat(NN, false, false, false, false),
Opcode.BZ to InstructionFormat(BW, true, false, false, true ),
@ -247,25 +271,29 @@ val instructionFormats = mutableMapOf(
Opcode.BGE to InstructionFormat(BW, true, true, false, true ),
Opcode.BGES to InstructionFormat(BW, true, true, false, true ),
Opcode.NEG to InstructionFormat(BW, true, true, false, false),
Opcode.INC to InstructionFormat(BW, true, false, false, false),
Opcode.DEC to InstructionFormat(BW, true, false, false, false),
Opcode.NEG to InstructionFormat(BW, true, false, false, false),
Opcode.ADD to InstructionFormat(BW, true, true, true, false),
Opcode.SUB to InstructionFormat(BW, true, true, true, false),
Opcode.MUL to InstructionFormat(BW, true, true, true, false),
Opcode.DIV to InstructionFormat(BW, true, true, true, false),
Opcode.EXT to InstructionFormat(BW, true, true, false, false),
Opcode.EXTS to InstructionFormat(BW, true, true, false, false),
Opcode.MOD to InstructionFormat(BW, true, true, true, false),
Opcode.EXT to InstructionFormat(BW, true, false, false, false),
Opcode.EXTS to InstructionFormat(BW, true, false, false, false),
Opcode.INV to InstructionFormat(BW, true, true, false, false),
Opcode.AND to InstructionFormat(BW, true, true, true, false),
Opcode.OR to InstructionFormat(BW, true, true, true, false),
Opcode.XOR to InstructionFormat(BW, true, true, true, false),
Opcode.LSR to InstructionFormat(BW, true, true, false, false),
Opcode.LSL to InstructionFormat(BW, true, true, false, false),
Opcode.ROR to InstructionFormat(BW, true, true, false, false),
Opcode.ROL to InstructionFormat(BW, true, true, false, false),
Opcode.LSR to InstructionFormat(BW, true, false, false, false),
Opcode.LSL to InstructionFormat(BW, true, false, false, false),
Opcode.ROR to InstructionFormat(BW, true, false, false, false),
Opcode.ROL to InstructionFormat(BW, true, false, false, false),
Opcode.COPY to InstructionFormat(NN, true, true, false, true ),
Opcode.COPYZ to InstructionFormat(NN, true, true, false, false),
Opcode.SWAP to InstructionFormat(BW, true, true, false, false),
Opcode.PUSH to InstructionFormat(BW, true, false, false, false),
Opcode.POP to InstructionFormat(BW, true, false, false, false),
Opcode.BREAKPOINT to InstructionFormat(NN, false, false, false, false)
)

View File

@ -15,6 +15,7 @@ class VirtualMachine(val memory: Memory, program: List<Instruction>) {
val registers = Registers()
val program: Array<Instruction> = program.toTypedArray()
val callStack = Stack<Int>()
val valueStack = Stack<Int>() // max 128 entries
var pc = 0
var stepCount = 0
@ -91,8 +92,8 @@ class VirtualMachine(val memory: Memory, program: List<Instruction>) {
Opcode.STOREZI -> InsSTOREZI(ins)
Opcode.JUMP -> InsJUMP(ins)
Opcode.JUMPI -> InsJUMPI(ins)
Opcode.GOSUB -> InsGOSUB(ins)
Opcode.GOSUBI -> InsGOSUBI(ins)
Opcode.CALL -> InsCALL(ins)
Opcode.CALLI -> InsCALLI(ins)
Opcode.SYSCALL -> InsSYSCALL(ins)
Opcode.RETURN -> InsRETURN()
Opcode.BZ -> InsBZ(ins)
@ -107,14 +108,16 @@ class VirtualMachine(val memory: Memory, program: List<Instruction>) {
Opcode.BLES -> InsBLES(ins)
Opcode.BGE -> InsBGEU(ins)
Opcode.BGES -> InsBGES(ins)
Opcode.INC -> InsINC(ins)
Opcode.DEC -> InsDEC(ins)
Opcode.NEG -> InsNEG(ins)
Opcode.ADD -> InsADD(ins)
Opcode.SUB -> InsSUB(ins)
Opcode.MUL -> InsMul(ins)
Opcode.DIV -> InsDiv(ins)
Opcode.MUL -> InsMUL(ins)
Opcode.DIV -> InsDIV(ins)
Opcode.MOD -> InsMOD(ins)
Opcode.EXT -> InsEXT(ins)
Opcode.EXTS -> InsEXTS(ins)
Opcode.INV -> InsINV(ins)
Opcode.AND -> InsAND(ins)
Opcode.OR -> InsOR(ins)
Opcode.XOR -> InsXOR(ins)
@ -123,6 +126,8 @@ class VirtualMachine(val memory: Memory, program: List<Instruction>) {
Opcode.ROR -> InsROR(ins)
Opcode.ROL -> InsROL(ins)
Opcode.SWAP -> InsSWAP(ins)
Opcode.PUSH -> InsPUSH(ins)
Opcode.POP -> InsPOP(ins)
Opcode.COPY -> InsCOPY(ins)
Opcode.COPYZ -> InsCOPYZ(ins)
Opcode.BREAKPOINT -> InsBREAKPOINT()
@ -130,6 +135,35 @@ class VirtualMachine(val memory: Memory, program: List<Instruction>) {
}
}
private fun InsPUSH(ins: Instruction) {
if(valueStack.size>=128)
throw StackOverflowError("valuestack limit 128 exceeded")
val value = when(ins.type!!) {
VmDataType.BYTE -> {
registers.getB(ins.reg1!!).toInt()
}
VmDataType.WORD -> {
registers.getW(ins.reg1!!).toInt()
}
}
valueStack.push(value)
pc++
}
private fun InsPOP(ins: Instruction) {
val value = valueStack.pop()
when(ins.type!!) {
VmDataType.BYTE -> {
registers.setB(ins.reg1!!, value.toUByte())
}
VmDataType.WORD -> {
registers.setW(ins.reg1!!, value.toUShort())
}
}
pc++
}
private fun InsSYSCALL(ins: Instruction) {
val call = Syscall.values()[ins.value!!]
SysCalls.call(call, this)
@ -143,51 +177,51 @@ class VirtualMachine(val memory: Memory, program: List<Instruction>) {
private fun InsLOAD(i: Instruction) {
when(i.type!!) {
DataType.BYTE -> registers.setB(i.reg1!!, i.value!!.toUByte())
DataType.WORD -> registers.setW(i.reg1!!, i.value!!.toUShort())
VmDataType.BYTE -> registers.setB(i.reg1!!, i.value!!.toUByte())
VmDataType.WORD -> registers.setW(i.reg1!!, i.value!!.toUShort())
}
pc++
}
private fun InsLOADM(i: Instruction) {
when(i.type!!) {
DataType.BYTE -> registers.setB(i.reg1!!, memory.getB(i.value!!))
DataType.WORD -> registers.setW(i.reg1!!, memory.getW(i.value!!))
VmDataType.BYTE -> registers.setB(i.reg1!!, memory.getB(i.value!!))
VmDataType.WORD -> registers.setW(i.reg1!!, memory.getW(i.value!!))
}
pc++
}
private fun InsLOADI(i: Instruction) {
when(i.type!!) {
DataType.BYTE -> registers.setB(i.reg1!!, memory.getB(registers.getW(i.reg2!!).toInt()))
DataType.WORD -> registers.setW(i.reg1!!, memory.getW(registers.getW(i.reg2!!).toInt()))
VmDataType.BYTE -> registers.setB(i.reg1!!, memory.getB(registers.getW(i.reg2!!).toInt()))
VmDataType.WORD -> registers.setW(i.reg1!!, memory.getW(registers.getW(i.reg2!!).toInt()))
}
pc++
}
private fun InsLOADX(i: Instruction) {
when (i.type!!) {
DataType.BYTE -> registers.setB(i.reg1!!, memory.getB(i.value!! + registers.getW(i.reg2!!).toInt()))
DataType.WORD -> registers.setW(i.reg1!!, memory.getW(i.value!! + registers.getW(i.reg2!!).toInt()))
VmDataType.BYTE -> registers.setB(i.reg1!!, memory.getB(i.value!! + registers.getW(i.reg2!!).toInt()))
VmDataType.WORD -> registers.setW(i.reg1!!, memory.getW(i.value!! + registers.getW(i.reg2!!).toInt()))
}
pc++
}
private fun InsLOADR(i: Instruction) {
when(i.type!!) {
DataType.BYTE -> registers.setB(i.reg1!!, registers.getB(i.reg2!!))
DataType.WORD -> registers.setW(i.reg1!!, registers.getW(i.reg2!!))
VmDataType.BYTE -> registers.setB(i.reg1!!, registers.getB(i.reg2!!))
VmDataType.WORD -> registers.setW(i.reg1!!, registers.getW(i.reg2!!))
}
pc++
}
private fun InsSWAPREG(i: Instruction) {
when(i.type!!) {
DataType.BYTE -> {
VmDataType.BYTE -> {
val oldR2 = registers.getB(i.reg2!!)
registers.setB(i.reg2, registers.getB(i.reg1!!))
registers.setB(i.reg1, oldR2)
}
DataType.WORD -> {
VmDataType.WORD -> {
val oldR2 = registers.getW(i.reg2!!)
registers.setW(i.reg2, registers.getW(i.reg1!!))
registers.setW(i.reg1, oldR2)
@ -198,46 +232,46 @@ class VirtualMachine(val memory: Memory, program: List<Instruction>) {
private fun InsSTOREM(i: Instruction) {
when(i.type!!) {
DataType.BYTE -> memory.setB(i.value!!, registers.getB(i.reg1!!))
DataType.WORD -> memory.setW(i.value!!, registers.getW(i.reg1!!))
VmDataType.BYTE -> memory.setB(i.value!!, registers.getB(i.reg1!!))
VmDataType.WORD -> memory.setW(i.value!!, registers.getW(i.reg1!!))
}
pc++
}
private fun InsSTOREI(i: Instruction) {
when (i.type!!) {
DataType.BYTE -> memory.setB(registers.getW(i.reg2!!).toInt(), registers.getB(i.reg1!!))
DataType.WORD -> memory.setW(registers.getW(i.reg2!!).toInt(), registers.getW(i.reg1!!))
VmDataType.BYTE -> memory.setB(registers.getW(i.reg2!!).toInt(), registers.getB(i.reg1!!))
VmDataType.WORD -> memory.setW(registers.getW(i.reg2!!).toInt(), registers.getW(i.reg1!!))
}
pc++
}
private fun InsSTOREX(i: Instruction) {
when (i.type!!) {
DataType.BYTE -> memory.setB(registers.getW(i.reg2!!).toInt() + i.value!!, registers.getB(i.reg1!!))
DataType.WORD -> memory.setW(registers.getW(i.reg2!!).toInt() + i.value!!, registers.getW(i.reg1!!))
VmDataType.BYTE -> memory.setB(registers.getW(i.reg2!!).toInt() + i.value!!, registers.getB(i.reg1!!))
VmDataType.WORD -> memory.setW(registers.getW(i.reg2!!).toInt() + i.value!!, registers.getW(i.reg1!!))
}
pc++
}
private fun InsSTOREZ(i: Instruction) {
when(i.type!!) {
DataType.BYTE -> memory.setB(i.value!!, 0u)
DataType.WORD -> memory.setW(i.value!!, 0u)
VmDataType.BYTE -> memory.setB(i.value!!, 0u)
VmDataType.WORD -> memory.setW(i.value!!, 0u)
}
}
private fun InsSTOREZI(i: Instruction) {
when (i.type!!) {
DataType.BYTE -> memory.setB(registers.getW(i.reg2!!).toInt(), 0u)
DataType.WORD -> memory.setW(registers.getW(i.reg2!!).toInt(), 0u)
VmDataType.BYTE -> memory.setB(registers.getW(i.reg2!!).toInt(), 0u)
VmDataType.WORD -> memory.setW(registers.getW(i.reg2!!).toInt(), 0u)
}
pc++
}
private fun InsSTOREZX(i: Instruction) {
when (i.type!!) {
DataType.BYTE -> memory.setB(registers.getW(i.reg2!!).toInt() + i.value!!, 0u)
DataType.WORD -> memory.setW(registers.getW(i.reg2!!).toInt() + i.value!!, 0u)
VmDataType.BYTE -> memory.setB(registers.getW(i.reg2!!).toInt() + i.value!!, 0u)
VmDataType.WORD -> memory.setW(registers.getW(i.reg2!!).toInt() + i.value!!, 0u)
}
pc++
}
@ -250,12 +284,12 @@ class VirtualMachine(val memory: Memory, program: List<Instruction>) {
pc = registers.getW(i.reg1!!).toInt()
}
private fun InsGOSUB(i: Instruction) {
private fun InsCALL(i: Instruction) {
callStack.push(pc+1)
pc = i.value!!
}
private fun InsGOSUBI(i: Instruction) {
private fun InsCALLI(i: Instruction) {
callStack.push(pc+1)
pc = registers.getW(i.reg1!!).toInt()
}
@ -269,13 +303,13 @@ class VirtualMachine(val memory: Memory, program: List<Instruction>) {
private fun InsBZ(i: Instruction) {
when(i.type!!) {
DataType.BYTE -> {
VmDataType.BYTE -> {
if(registers.getB(i.reg1!!)==0.toUByte())
pc = i.value!!
else
pc++
}
DataType.WORD -> {
VmDataType.WORD -> {
if(registers.getW(i.reg1!!)==0.toUShort())
pc = i.value!!
else
@ -286,13 +320,13 @@ class VirtualMachine(val memory: Memory, program: List<Instruction>) {
private fun InsBNZ(i: Instruction) {
when(i.type!!) {
DataType.BYTE -> {
VmDataType.BYTE -> {
if(registers.getB(i.reg1!!)!=0.toUByte())
pc = i.value!!
else
pc++
}
DataType.WORD -> {
VmDataType.WORD -> {
if(registers.getW(i.reg1!!)!=0.toUShort())
pc = i.value!!
else
@ -311,24 +345,24 @@ class VirtualMachine(val memory: Memory, program: List<Instruction>) {
private fun getBranchOperands(i: Instruction): Pair<Int, Int> {
return when(i.type) {
DataType.BYTE -> Pair(registers.getB(i.reg1!!).toInt(), registers.getB(i.reg2!!).toInt())
DataType.WORD -> Pair(registers.getW(i.reg1!!).toInt(), registers.getW(i.reg2!!).toInt())
VmDataType.BYTE -> Pair(registers.getB(i.reg1!!).toInt(), registers.getB(i.reg2!!).toInt())
VmDataType.WORD -> Pair(registers.getW(i.reg1!!).toInt(), registers.getW(i.reg2!!).toInt())
null -> throw IllegalArgumentException("need type for branch instruction")
}
}
private fun getBranchOperandsU(i: Instruction): Pair<UInt, UInt> {
return when(i.type) {
DataType.BYTE -> Pair(registers.getB(i.reg1!!).toUInt(), registers.getB(i.reg2!!).toUInt())
DataType.WORD -> Pair(registers.getW(i.reg1!!).toUInt(), registers.getW(i.reg2!!).toUInt())
VmDataType.BYTE -> Pair(registers.getB(i.reg1!!).toUInt(), registers.getB(i.reg2!!).toUInt())
VmDataType.WORD -> Pair(registers.getW(i.reg1!!).toUInt(), registers.getW(i.reg2!!).toUInt())
null -> throw IllegalArgumentException("need type for branch instruction")
}
}
private fun getLogicalOperandsU(i: Instruction): Pair<UInt, UInt> {
return when(i.type) {
DataType.BYTE -> Pair(registers.getB(i.reg2!!).toUInt(), registers.getB(i.reg3!!).toUInt())
DataType.WORD -> Pair(registers.getW(i.reg2!!).toUInt(), registers.getW(i.reg3!!).toUInt())
VmDataType.BYTE -> Pair(registers.getB(i.reg2!!).toUInt(), registers.getB(i.reg3!!).toUInt())
VmDataType.WORD -> Pair(registers.getW(i.reg2!!).toUInt(), registers.getW(i.reg3!!).toUInt())
null -> throw IllegalArgumentException("need type for logical instruction")
}
}
@ -408,34 +442,58 @@ class VirtualMachine(val memory: Memory, program: List<Instruction>) {
pc++
}
private fun InsINC(i: Instruction) {
when(i.type!!) {
VmDataType.BYTE -> registers.setB(i.reg1!!, (registers.getB(i.reg1)+1u).toUByte())
VmDataType.WORD -> registers.setW(i.reg1!!, (registers.getW(i.reg1)+1u).toUShort())
}
pc++
}
private fun InsDEC(i: Instruction) {
when(i.type!!) {
VmDataType.BYTE -> registers.setB(i.reg1!!, (registers.getB(i.reg1)-1u).toUByte())
VmDataType.WORD -> registers.setW(i.reg1!!, (registers.getW(i.reg1)-1u).toUShort())
}
pc++
}
private fun InsNEG(i: Instruction) {
when(i.type!!) {
DataType.BYTE -> registers.setB(i.reg1!!, (-registers.getB(i.reg2!!).toInt()).toUByte())
DataType.WORD -> registers.setW(i.reg1!!, (-registers.getW(i.reg2!!).toInt()).toUShort())
VmDataType.BYTE -> registers.setB(i.reg1!!, (-registers.getB(i.reg1).toInt()).toUByte())
VmDataType.WORD -> registers.setW(i.reg1!!, (-registers.getW(i.reg1).toInt()).toUShort())
}
pc++
}
private fun InsADD(i: Instruction) {
when(i.type!!) {
DataType.BYTE -> arithByte("+", i.reg1!!, i.reg2!!, i.reg3!!, null)
DataType.WORD -> arithWord("+", i.reg1!!, i.reg2!!, i.reg3!!, null)
VmDataType.BYTE -> arithByte("+", i.reg1!!, i.reg2!!, i.reg3!!, null)
VmDataType.WORD -> arithWord("+", i.reg1!!, i.reg2!!, i.reg3!!, null)
}
pc++
}
private fun InsMul(i: Instruction) {
private fun InsMUL(i: Instruction) {
when(i.type!!) {
DataType.BYTE -> arithByte("*", i.reg1!!, i.reg2!!, i.reg3!!, null)
DataType.WORD -> arithWord("*", i.reg1!!, i.reg2!!, i.reg3!!, null)
VmDataType.BYTE -> arithByte("*", i.reg1!!, i.reg2!!, i.reg3!!, null)
VmDataType.WORD -> arithWord("*", i.reg1!!, i.reg2!!, i.reg3!!, null)
}
pc++
}
private fun InsDiv(i: Instruction) {
private fun InsDIV(i: Instruction) {
when(i.type!!) {
DataType.BYTE -> arithByte("/", i.reg1!!, i.reg2!!, i.reg3!!, null)
DataType.WORD -> arithWord("/", i.reg1!!, i.reg2!!, i.reg3!!, null)
VmDataType.BYTE -> arithByte("/", i.reg1!!, i.reg2!!, i.reg3!!, null)
VmDataType.WORD -> arithWord("/", i.reg1!!, i.reg2!!, i.reg3!!, null)
}
pc++
}
private fun InsMOD(i: Instruction) {
when(i.type!!) {
VmDataType.BYTE -> arithByte("%", i.reg1!!, i.reg2!!, i.reg3!!, null)
VmDataType.WORD -> arithWord("%", i.reg1!!, i.reg2!!, i.reg3!!, null)
}
pc++
}
@ -451,6 +509,10 @@ class VirtualMachine(val memory: Memory, program: List<Instruction>) {
if(right==0.toUByte()) 0xffu
else left / right
}
"%" -> {
if(right==0.toUByte()) 0xffu
else left % right
}
else -> TODO("operator $operator")
}
registers.setB(reg1, result.toUByte())
@ -467,6 +529,10 @@ class VirtualMachine(val memory: Memory, program: List<Instruction>) {
if(right==0.toUShort()) 0xffffu
else left / right
}
"%" -> {
if(right==0.toUShort()) 0xffffu
else left % right
}
else -> TODO("operator $operator")
}
registers.setW(reg1, result.toUShort())
@ -474,32 +540,24 @@ class VirtualMachine(val memory: Memory, program: List<Instruction>) {
private fun InsSUB(i: Instruction) {
when(i.type!!) {
DataType.BYTE -> arithByte("-", i.reg1!!, i.reg2!!, i.reg3!!, null)
DataType.WORD -> arithWord("-", i.reg1!!, i.reg2!!, i.reg3!!, null)
VmDataType.BYTE -> arithByte("-", i.reg1!!, i.reg2!!, i.reg3!!, null)
VmDataType.WORD -> arithWord("-", i.reg1!!, i.reg2!!, i.reg3!!, null)
}
pc++
}
private fun InsEXT(i: Instruction) {
when(i.type!!){
DataType.BYTE -> registers.setW(i.reg1!!, registers.getB(i.reg2!!).toUShort())
DataType.WORD -> TODO("ext.w requires 32 bits registers")
VmDataType.BYTE -> registers.setW(i.reg1!!, registers.getB(i.reg1).toUShort())
VmDataType.WORD -> TODO("ext.w requires 32 bits registers")
}
pc++
}
private fun InsEXTS(i: Instruction) {
when(i.type!!){
DataType.BYTE -> registers.setW(i.reg1!!, (registers.getB(i.reg2!!).toByte()).toUShort())
DataType.WORD -> TODO("ext.w requires 32 bits registers")
}
pc++
}
private fun InsINV(i: Instruction) {
when(i.type!!) {
DataType.BYTE -> registers.setB(i.reg1!!, registers.getB(i.reg2!!).inv())
DataType.WORD -> registers.setW(i.reg1!!, registers.getW(i.reg2!!).inv())
VmDataType.BYTE -> registers.setW(i.reg1!!, (registers.getB(i.reg1).toByte()).toUShort())
VmDataType.WORD -> TODO("ext.w requires 32 bits registers")
}
pc++
}
@ -507,8 +565,8 @@ class VirtualMachine(val memory: Memory, program: List<Instruction>) {
private fun InsAND(i: Instruction) {
val (left: UInt, right: UInt) = getLogicalOperandsU(i)
when(i.type!!) {
DataType.BYTE -> registers.setB(i.reg1!!, (left and right).toUByte())
DataType.WORD -> registers.setW(i.reg1!!, (left and right).toUShort())
VmDataType.BYTE -> registers.setB(i.reg1!!, (left and right).toUByte())
VmDataType.WORD -> registers.setW(i.reg1!!, (left and right).toUShort())
}
pc++
}
@ -516,8 +574,8 @@ class VirtualMachine(val memory: Memory, program: List<Instruction>) {
private fun InsOR(i: Instruction) {
val (left: UInt, right: UInt) = getLogicalOperandsU(i)
when(i.type!!) {
DataType.BYTE -> registers.setB(i.reg1!!, (left or right).toUByte())
DataType.WORD -> registers.setW(i.reg1!!, (left or right).toUShort())
VmDataType.BYTE -> registers.setB(i.reg1!!, (left or right).toUByte())
VmDataType.WORD -> registers.setW(i.reg1!!, (left or right).toUShort())
}
pc++
}
@ -525,40 +583,40 @@ class VirtualMachine(val memory: Memory, program: List<Instruction>) {
private fun InsXOR(i: Instruction) {
val (left: UInt, right: UInt) = getLogicalOperandsU(i)
when(i.type!!) {
DataType.BYTE -> registers.setB(i.reg1!!, (left xor right).toUByte())
DataType.WORD -> registers.setW(i.reg1!!, (left xor right).toUShort())
VmDataType.BYTE -> registers.setB(i.reg1!!, (left xor right).toUByte())
VmDataType.WORD -> registers.setW(i.reg1!!, (left xor right).toUShort())
}
pc++
}
private fun InsLSR(i: Instruction) {
when(i.type!!) {
DataType.BYTE -> registers.setB(i.reg1!!, (registers.getB(i.reg2!!).toInt() ushr 1).toUByte())
DataType.WORD -> registers.setW(i.reg1!!, (registers.getW(i.reg2!!).toInt() ushr 1).toUShort())
VmDataType.BYTE -> registers.setB(i.reg1!!, (registers.getB(i.reg2!!).toInt() ushr 1).toUByte())
VmDataType.WORD -> registers.setW(i.reg1!!, (registers.getW(i.reg2!!).toInt() ushr 1).toUShort())
}
pc++
}
private fun InsLSL(i: Instruction) {
when(i.type!!) {
DataType.BYTE -> registers.setB(i.reg1!!, (registers.getB(i.reg2!!).toInt() shl 1).toUByte())
DataType.WORD -> registers.setW(i.reg1!!, (registers.getW(i.reg2!!).toInt() shl 1).toUShort())
VmDataType.BYTE -> registers.setB(i.reg1!!, (registers.getB(i.reg2!!).toInt() shl 1).toUByte())
VmDataType.WORD -> registers.setW(i.reg1!!, (registers.getW(i.reg2!!).toInt() shl 1).toUShort())
}
pc++
}
private fun InsROR(i: Instruction) {
when(i.type!!) {
DataType.BYTE -> registers.setB(i.reg1!!, (registers.getB(i.reg2!!).toInt().rotateRight(1).toUByte()))
DataType.WORD -> registers.setW(i.reg1!!, (registers.getW(i.reg2!!).toInt().rotateRight(1).toUShort()))
VmDataType.BYTE -> registers.setB(i.reg1!!, (registers.getB(i.reg2!!).toInt().rotateRight(1).toUByte()))
VmDataType.WORD -> registers.setW(i.reg1!!, (registers.getW(i.reg2!!).toInt().rotateRight(1).toUShort()))
}
pc++
}
private fun InsROL(i: Instruction) {
when(i.type!!) {
DataType.BYTE -> registers.setB(i.reg1!!, (registers.getB(i.reg2!!).toInt().rotateLeft(1).toUByte()))
DataType.WORD -> registers.setW(i.reg1!!, (registers.getW(i.reg2!!).toInt().rotateLeft(1).toUShort()))
VmDataType.BYTE -> registers.setB(i.reg1!!, (registers.getB(i.reg2!!).toInt().rotateLeft(1).toUByte()))
VmDataType.WORD -> registers.setW(i.reg1!!, (registers.getW(i.reg2!!).toInt().rotateLeft(1).toUShort()))
}
pc++
}
@ -594,12 +652,12 @@ class VirtualMachine(val memory: Memory, program: List<Instruction>) {
private fun InsSWAP(i: Instruction) {
when(i.type!!) {
DataType.BYTE -> {
VmDataType.BYTE -> {
val value = registers.getW(i.reg2!!)
val newValue = value.toUByte()*256u + (value.toInt() ushr 8).toUInt()
registers.setW(i.reg1!!, newValue.toUShort())
}
DataType.WORD -> TODO("swap.w requires 32-bits registers")
VmDataType.WORD -> TODO("swap.w requires 32-bits registers")
}
pc++
}