mirror of
https://github.com/irmen/prog8.git
synced 2025-01-10 20:30:23 +00:00
working on vm codegen
This commit is contained in:
parent
dbc7ad2ec4
commit
27f6d47efa
@ -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>
|
||||
|
@ -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() {}
|
||||
}
|
@ -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() {}
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
@ -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")
|
||||
|
@ -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)
|
||||
|
@ -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}")
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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"
|
||||
|
@ -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?).
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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")
|
||||
}
|
||||
}
|
||||
|
@ -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() {
|
||||
|
@ -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)
|
||||
)
|
||||
|
@ -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++
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user