vm: new translation of IRProgram into vm program list

This commit is contained in:
Irmen de Jong 2022-09-27 01:50:00 +02:00
parent 11c000f764
commit db1aa8fcbd
8 changed files with 331 additions and 143 deletions

View File

@ -25,12 +25,9 @@ main {
}
}"""
val target = VMTarget()
val result = compileText(target, true, src, writeAssembly = true, keepIR=true)!!
val result = compileText(target, true, src, writeAssembly = true)!!
val virtfile = result.compilationOptions.outputDir.resolve(result.program.name + ".p8ir")
VmRunner().runProgram(virtfile.readText())
val result2 = compileText(target, true, src, writeAssembly = true, keepIR=false)!!
val virtfile2 = result2.compilationOptions.outputDir.resolve(result2.program.name + ".p8ir")
VmRunner().runProgram(virtfile2.readText())
}
test("compile virtual: array with pointers") {
@ -50,12 +47,9 @@ main {
val othertarget = Cx16Target()
compileText(othertarget, true, src, writeAssembly = true, keepIR=true) shouldNotBe null
val target = VMTarget()
val result = compileText(target, true, src, writeAssembly = true, keepIR=true)!!
val result = compileText(target, true, src, writeAssembly = true)!!
val virtfile = result.compilationOptions.outputDir.resolve(result.program.name + ".p8ir")
VmRunner().runProgram(virtfile.readText())
val result2 = compileText(target, true, src, writeAssembly = true, keepIR=false)!!
val virtfile2 = result2.compilationOptions.outputDir.resolve(result2.program.name + ".p8ir")
VmRunner().runProgram(virtfile2.readText())
}
test("compile virtual: str args and return type") {
@ -71,12 +65,9 @@ main {
}
}"""
val target = VMTarget()
val result = compileText(target, true, src, writeAssembly = true, keepIR=true)!!
val result = compileText(target, true, src, writeAssembly = true)!!
val virtfile = result.compilationOptions.outputDir.resolve(result.program.name + ".p8ir")
VmRunner().runProgram(virtfile.readText())
val result2 = compileText(target, true, src, writeAssembly = true, keepIR=false)!!
val virtfile2 = result2.compilationOptions.outputDir.resolve(result2.program.name + ".p8ir")
VmRunner().runProgram(virtfile2.readText())
}
test("compile virtual: nested labels") {
@ -114,18 +105,13 @@ mylabel_inside:
}"""
val target = VMTarget()
val result = compileText(target, true, src, writeAssembly = true, keepIR=true)!!
val result = compileText(target, true, src, writeAssembly = true)!!
val virtfile = result.compilationOptions.outputDir.resolve(result.program.name + ".p8ir")
VmRunner().runProgram(virtfile.readText())
val result2 = compileText(target, true, src, writeAssembly = true, keepIR=false)!!
val virtfile2 = result2.compilationOptions.outputDir.resolve(result2.program.name + ".p8ir")
VmRunner().runProgram(virtfile2.readText())
}
test("case sensitive symbols") {
val src = """
%zeropage basicsafe
main {
sub start() {
ubyte bytevar = 11 ; var at 0
@ -142,18 +128,11 @@ skipLABEL:
val othertarget = Cx16Target()
compileText(othertarget, true, src, writeAssembly = true, keepIR=true) shouldNotBe null
val target = VMTarget()
val result = compileText(target, true, src, writeAssembly = true, keepIR=true)!!
val result = compileText(target, true, src, writeAssembly = true)!!
val virtfile = result.compilationOptions.outputDir.resolve(result.program.name + ".p8ir")
VmRunner().runAndTestProgram(virtfile.readText()) { vm ->
vm.memory.getUB(0) shouldBe 42u
vm.memory.getUB(3) shouldBe 66u
}
val result2 = compileText(target, true, src, writeAssembly = true, keepIR=false)!!
val virtfile2 = result2.compilationOptions.outputDir.resolve(result2.program.name + ".p8ir")
VmRunner().runAndTestProgram(virtfile2.readText()) { vm ->
vm.memory.getUB(0) shouldBe 42u
vm.memory.getUB(3) shouldBe 66u
}
}
})

View File

@ -3,7 +3,8 @@ TODO
For next release
^^^^^^^^^^^^^^^^
- vm: actually translate IRProgram to vm program list again.
- vm: replace addAssemblyToProgram() by call to IRFileLoader's logic, instead of duplicating it.
- vm: fix TestCompilerVirtual executing wrong code? VM graphics window pops up...
- docs: modify compiler arch picture once this is done.
...

View File

@ -61,24 +61,24 @@ BRANCHING
---------
All have type b or w except the branches that only check status bits.
bstcc location - branch to location if Status bit Carry is Clear
bstcs location - branch to location if Status bit Carry is Set
bsteq location - branch to location if Status bit Zero is set
bstne location - branch to location if Status bit Zero is not set
bstneg location - branch to location if Status bit Negative is not set
bstpos location - branch to location if Status bit Negative is not set
bz reg1, location - branch to location if reg1 is zero
bnz reg1, location - branch to location if reg1 is not zero
beq reg1, reg2, location - jump to location in program given by location, if reg1 == reg2
bne reg1, reg2, location - jump to location in program given by location, if reg1 != reg2
blt reg1, reg2, location - jump to location in program given by location, if reg1 < reg2 (unsigned)
blts reg1, reg2, location - jump to location in program given by location, if reg1 < reg2 (signed)
ble reg1, reg2, location - jump to location in program given by location, if reg1 <= reg2 (unsigned)
bles reg1, reg2, location - jump to location in program given by location, if reg1 <= reg2 (signed)
bgt reg1, reg2, location - jump to location in program given by location, if reg1 > reg2 (unsigned)
bgts reg1, reg2, location - jump to location in program given by location, if reg1 > reg2 (signed)
bge reg1, reg2, location - jump to location in program given by location, if reg1 >= reg2 (unsigned)
bges reg1, reg2, location - jump to location in program given by location, if reg1 >= reg2 (signed)
bstcc address - branch to location if Status bit Carry is Clear
bstcs address - branch to location if Status bit Carry is Set
bsteq address - branch to location if Status bit Zero is set
bstne address - branch to location if Status bit Zero is not set
bstneg address - branch to location if Status bit Negative is not set
bstpos address - branch to location if Status bit Negative is not set
bz reg1, address - branch to location if reg1 is zero
bnz reg1, address - branch to location if reg1 is not zero
beq reg1, reg2, address - jump to location in program given by location, if reg1 == reg2
bne reg1, reg2, address - jump to location in program given by location, if reg1 != reg2
blt reg1, reg2, address - jump to location in program given by location, if reg1 < reg2 (unsigned)
blts reg1, reg2, address - jump to location in program given by location, if reg1 < reg2 (signed)
ble reg1, reg2, address - jump to location in program given by location, if reg1 <= reg2 (unsigned)
bles reg1, reg2, address - jump to location in program given by location, if reg1 <= reg2 (signed)
bgt reg1, reg2, address - jump to location in program given by location, if reg1 > reg2 (unsigned)
bgts reg1, reg2, address - jump to location in program given by location, if reg1 > reg2 (signed)
bge reg1, reg2, address - jump to location in program given by location, if reg1 >= reg2 (unsigned)
bges reg1, reg2, address - jump to location in program given by location, if reg1 >= reg2 (signed)
seq reg1, reg2 - set reg=1 if reg1 == reg2, otherwise set reg1=0
sne reg1, reg2 - set reg=1 if reg1 != reg2, otherwise set reg1=0
slt reg1, reg2 - set reg=1 if reg1 < reg2 (unsigned), otherwise set reg1=0
@ -381,7 +381,25 @@ val OpcodesWithAddress = setOf(
Opcode.ROLM,
Opcode.RORM,
Opcode.ROXLM,
Opcode.ROXRM
Opcode.ROXRM,
Opcode.BSTCC,
Opcode.BSTCS,
Opcode.BSTEQ,
Opcode.BSTNE,
Opcode.BSTNEG,
Opcode.BSTPOS,
Opcode.BZ,
Opcode.BNZ,
Opcode.BEQ,
Opcode.BNE,
Opcode.BLT,
Opcode.BLTS,
Opcode.BLE,
Opcode.BLES,
Opcode.BGT,
Opcode.BGTS,
Opcode.BGE,
Opcode.BGES
)
val OpcodesForCpuRegisters = setOf(
@ -463,12 +481,12 @@ data class IRInstruction(
if (type==VmDataType.FLOAT) {
if(format.fpValue && (fpValue==null && labelSymbol==null))
throw IllegalArgumentException("$opcode: missing a fp-value or labelsymbol")
throw IllegalArgumentException("missing a fp-value or labelsymbol")
} else {
if(format.value && (value==null && labelSymbol==null))
throw IllegalArgumentException("$opcode: missing a value or labelsymbol")
throw IllegalArgumentException("missing a value or labelsymbol")
if (fpReg1 != null || fpReg2 != null)
throw IllegalArgumentException("$opcode: integer point instruction can't use floating point registers")
throw IllegalArgumentException("integer point instruction can't use floating point registers")
}
reg1direction = format.reg1direction

View File

@ -1,4 +1,5 @@
import io.kotest.assertions.throwables.shouldThrow
import io.kotest.assertions.throwables.shouldThrowWithMessage
import io.kotest.core.spec.style.FunSpec
import io.kotest.matchers.shouldBe
import io.kotest.matchers.shouldNotBe
@ -73,25 +74,26 @@ class TestInstructions: FunSpec({
test("missing type should fail") {
shouldThrow<IllegalArgumentException> {
IRInstruction(Opcode.BZ, reg1=42, value=9999)
IRInstruction(Opcode.BZ, reg1=42, value=99)
}
}
test("missing registers should fail") {
shouldThrow<IllegalArgumentException> {
IRInstruction(Opcode.BZ, VmDataType.BYTE, value=9999)
shouldThrowWithMessage<IllegalArgumentException>("missing a register (int)") {
IRInstruction(Opcode.BZ, VmDataType.BYTE, value=99)
}
}
test("missing value should fail") {
shouldThrow<IllegalArgumentException> {
shouldThrowWithMessage<IllegalArgumentException>("missing a value or labelsymbol") {
IRInstruction(Opcode.BZ, VmDataType.BYTE, reg1=42)
}
}
test("all instructionformats") {
instructionFormats.size shouldBe Opcode.values().size
Opcode.values().forEach {
instructionFormats[it] shouldNotBe null
}
}
}
})

View File

@ -1,5 +1,6 @@
package prog8.vm
import prog8.code.StMemVar
import prog8.code.target.virtual.IVirtualMachineRunner
import prog8.intermediate.*
import java.awt.Color
@ -31,7 +32,7 @@ class BreakpointException(val pc: Int): Exception()
@Suppress("FunctionName")
class VirtualMachine(irProgram: IRProgram) {
val memory = Memory()
val program: Array<IRInstruction> = VmProgramLoader().load(irProgram, memory) // TODO convert irProgram
val program: Array<IRInstruction>
val registers = Registers()
val callStack = Stack<Int>()
val valueStack = Stack<UByte>() // max 128 entries
@ -40,11 +41,13 @@ class VirtualMachine(irProgram: IRProgram) {
var statusCarry = false
var statusZero = false
var statusNegative = false
val cx16virtualregsBaseAddress = 0xff00 // TODO obtain from irProgram
private val cx16virtualregsBaseAddress: Int
init {
program = VmProgramLoader().load(irProgram, memory).toTypedArray()
if(program.size>65536)
throw IllegalArgumentException("program cannot contain more than 65536 instructions")
cx16virtualregsBaseAddress = (irProgram.st.lookup("cx16.r0") as? StMemVar)?.address?.toInt() ?: 0xff02
}
fun run() {

View File

@ -1,28 +1,28 @@
package prog8.vm
import prog8.intermediate.*
import java.lang.IllegalArgumentException
import kotlin.IllegalArgumentException
class VmProgramLoader {
fun load(irProgram: IRProgram, memory: Memory): Array<IRInstruction> {
private val placeholders = mutableMapOf<Int, String>() // program index to symbolname
fun load(irProgram: IRProgram, memory: Memory): List<IRInstruction> {
// at long last, allocate the variables in memory.
placeholders.clear()
val allocations = VmVariableAllocator(irProgram.st, irProgram.encoding, irProgram.options.compTarget)
val symbolAddresses = allocations.allocations.toMutableMap()
val program = mutableListOf<IRInstruction>()
// TODO stuff the allocated variables into memory
if(!irProgram.options.dontReinitGlobals) {
irProgram.globalInits.forEach {
// TODO put global init line into program
}
}
if(!irProgram.options.dontReinitGlobals)
addToProgram(irProgram.globalInits, program, symbolAddresses)
// make sure that if there is a "main.start" entrypoint, we jump to it
irProgram.blocks.firstOrNull()?.let {
if(it.subroutines.any { sub -> sub.name=="main.start" }) {
program.add(IRInstruction(Opcode.JUMP, labelSymbol = "main.start"))
rememberPlaceholder("main.start", program.size)
program += IRInstruction(Opcode.JUMP, labelSymbol = "main.start")
}
}
@ -30,27 +30,257 @@ class VmProgramLoader {
if(block.address!=null)
throw IllegalArgumentException("blocks cannot have a load address for vm: ${block.name}")
block.inlineAssembly.forEach {
// TODO put it.assembly into program
}
block.inlineAssembly.forEach { addAssemblyToProgram(it, program) }
block.subroutines.forEach {
// TODO subroutine label ?
symbolAddresses[it.name] = program.size
it.chunks.forEach { chunk ->
if(chunk is IRInlineAsmChunk) {
// TODO put it.assembly into program
} else {
chunk.lines.forEach {
// TODO put line into program
if(chunk is IRInlineAsmChunk)
addAssemblyToProgram(chunk, program)
else
addToProgram(chunk.lines, program, symbolAddresses)
}
}
if(block.asmSubroutines.any())
throw IllegalArgumentException("vm currently does not support asmsubs: ${block.asmSubroutines.first().name}")
}
pass2replaceLabelsByProgIndex(program, symbolAddresses)
program.forEach {
if(it.opcode in OpcodesWithAddress && it.value==null) {
throw IllegalArgumentException("instruction missing numeric value, label not replaced? $it")
}
}
return program
}
private fun rememberPlaceholder(symbol: String, pc: Int) {
placeholders[pc] = symbol
}
private fun pass2replaceLabelsByProgIndex(
program: MutableList<IRInstruction>,
symbolAddresses: MutableMap<String, Int>
) {
for((line, label) in placeholders) {
val replacement = symbolAddresses[label]
if(replacement==null) {
// it could be an address + index: symbol+42
if('+' in label) {
val (symbol, indexStr) = label.split('+')
val index = indexStr.toInt()
val address = symbolAddresses.getValue(symbol) + index
program[line] = program[line].copy(value = address)
} else {
throw IllegalArgumentException("placeholder not found in labels: $label")
}
} else {
program[line] = program[line].copy(value = replacement)
}
}
}
private fun addToProgram(
lines: Iterable<IRCodeLine>,
program: MutableList<IRInstruction>,
symbolAddresses: MutableMap<String, Int>
) {
lines.map {
when(it) {
is IRInstruction -> {
it.labelSymbol?.let { symbol -> rememberPlaceholder(symbol, program.size)}
program += it
}
is IRCodeInlineBinary -> program += IRInstruction(Opcode.BINARYDATA, binaryData = it.data)
is IRCodeComment -> { /* just ignore */ }
is IRCodeLabel -> { symbolAddresses[it.name] = program.size }
}
}
}
private fun addAssemblyToProgram(
asmChunk: IRInlineAsmChunk,
program: MutableList<IRInstruction>,
) {
// TODO use IRFileReader.parseCodeLine instead of duplicating everything here
val instructionPattern = Regex("""([a-z]+)(\.b|\.w|\.f)?(.*)""", RegexOption.IGNORE_CASE)
asmChunk.assembly.lineSequence().forEach {
val line = it.trim()
val match = instructionPattern.matchEntire(line)
?: throw IllegalArgumentException("invalid IR instruction: $line in ${asmChunk.position}")
val (instr, typestr, rest) = match.destructured
val opcode = try {
Opcode.valueOf(instr.uppercase())
} catch (ax: IllegalArgumentException) {
throw IllegalArgumentException("invalid vmasm instruction: $instr", ax)
}
var type: VmDataType? = convertType(typestr)
val formats = instructionFormats.getValue(opcode)
val format: InstructionFormat
if(type !in formats) {
type = VmDataType.BYTE
format = if(type !in formats)
formats.getValue(null)
else
formats.getValue(type)
} else {
format = formats.getValue(type)
}
// parse the operands
val operands = rest.lowercase().split(",").toMutableList()
var reg1: Int? = null
var reg2: Int? = null
var reg3: Int? = null
var fpReg1: Int? = null
var fpReg2: Int? = null
var fpReg3: Int? = null
var value: Float? = null
var operand: String?
fun parseValueOrPlaceholder(operand: String, pc: Int, rest: String, restIndex: Int, opcode: Opcode): Float {
return if(operand.startsWith('_')) {
rememberPlaceholder(rest.split(",")[restIndex].trim().drop(1), pc)
0f
} else if(operand[0].isLetter()) {
rememberPlaceholder(rest.split(",")[restIndex].trim(), pc)
0f
} else
parseValue(opcode, operand, pc)
}
if(operands.isNotEmpty() && operands[0].isNotEmpty()) {
operand = operands.removeFirst().trim()
if(operand[0]=='r')
reg1 = operand.substring(1).toInt()
else if(operand[0]=='f' && operand[1]=='r')
fpReg1 = operand.substring(2).toInt()
else {
value = parseValueOrPlaceholder(operand, program.size, rest, 0, opcode)
operands.clear()
}
if(operands.isNotEmpty()) {
operand = operands.removeFirst().trim()
if(operand[0]=='r')
reg2 = operand.substring(1).toInt()
else if(operand[0]=='f' && operand[1]=='r')
fpReg2 = operand.substring(2).toInt()
else {
value = parseValueOrPlaceholder(operand, program.size, rest, 1, opcode)
operands.clear()
}
if(operands.isNotEmpty()) {
operand = operands.removeFirst().trim()
if(operand[0]=='r')
reg3 = operand.substring(1).toInt()
else if(operand[0]=='f' && operand[1]=='r')
fpReg3 = operand.substring(2).toInt()
else {
value = parseValueOrPlaceholder(operand, program.size, rest, 2, opcode)
operands.clear()
}
if(operands.isNotEmpty()) {
TODO("placeholder symbol? $operands rest=$rest'")
operands.clear()
}
}
}
}
block.asmSubroutines.forEach {
// TODO add asmsub to program
// shift the operands back into place
while(reg1==null && reg2!=null) {
reg1 = reg2
reg2 = reg3
reg3 = null
}
while(fpReg1==null && fpReg2!=null) {
fpReg1 = fpReg2
fpReg2 = fpReg3
fpReg3 = null
}
if(reg3!=null)
throw IllegalArgumentException("too many reg arguments $line")
if(fpReg3!=null)
throw IllegalArgumentException("too many fpreg arguments $line")
if(type!=null && type !in formats)
throw IllegalArgumentException("invalid type code for $line")
if(format.reg1 && reg1==null)
throw IllegalArgumentException("needs reg1 for $line")
if(format.reg2 && reg2==null)
throw IllegalArgumentException("needs reg2 for $line")
if(format.value && value==null)
throw IllegalArgumentException("needs value for $line")
if(!format.reg1 && reg1!=null)
throw IllegalArgumentException("invalid reg1 for $line")
if(!format.reg2 && reg2!=null)
throw IllegalArgumentException("invalid reg2 for $line")
if(value!=null && opcode !in OpcodesWithAddress) {
when (type) {
VmDataType.BYTE -> {
if (value < -128 || value > 255)
throw IllegalArgumentException("value out of range for byte: $value")
}
VmDataType.WORD -> {
if (value < -32768 || value > 65535)
throw IllegalArgumentException("value out of range for word: $value")
}
VmDataType.FLOAT -> {}
null -> {}
}
}
var floatValue: Float? = null
var intValue: Int? = null
if(format.value)
intValue = value!!.toInt()
if(format.fpValue)
floatValue = value!!
if(opcode in OpcodesForCpuRegisters) {
val regStr = rest.split(',').last().lowercase().trim()
val reg = if(regStr.startsWith('_')) regStr.substring(1) else regStr
if(reg !in setOf(
"a", "x", "y",
"ax", "ay", "xy",
"r0", "r1", "r2", "r3",
"r4", "r5", "r6", "r7",
"r8", "r9", "r10","r11",
"r12", "r13", "r14", "r15",
"pc", "pz", "pv","pn"))
throw IllegalArgumentException("invalid cpu reg: $reg")
program += IRInstruction(opcode, type, reg1, labelSymbol = reg)
} else {
program += IRInstruction(opcode, type, reg1, reg2, fpReg1, fpReg2, intValue, floatValue)
}
}
return emptyArray()
}
private fun parseValue(opcode: Opcode, value: String, pc: Int): Float {
return if(value.startsWith("-"))
-parseValue(opcode, value.substring(1), pc)
else if(value.startsWith('$'))
value.substring(1).toInt(16).toFloat()
else if(value.startsWith('%'))
value.substring(1).toInt(2).toFloat()
else if(value.startsWith("0x"))
value.substring(2).toInt(16).toFloat()
else if(value.startsWith('_') || value[0].isLetter())
throw IllegalArgumentException("attempt to parse non-numeric value $value")
else
value.toFloat()
}
private fun convertType(typestr: String): VmDataType? {
return when(typestr.lowercase()) {
"" -> null
".b" -> VmDataType.BYTE
".w" -> VmDataType.WORD
".f" -> VmDataType.FLOAT
else -> throw IllegalArgumentException("invalid type $typestr")
}
}
}

View File

@ -2,7 +2,6 @@ package prog8.vm
import prog8.code.core.*
import prog8.intermediate.IRSymbolTable
import prog8.intermediate.getTypeString
internal class VmVariableAllocator(val st: IRSymbolTable, val encoding: IStringEncoding, memsizer: IMemSizer) {
@ -35,63 +34,4 @@ internal class VmVariableAllocator(val st: IRSymbolTable, val encoding: IStringE
freeMemoryStart = nextLocation
}
fun asVmMemory(): List<Pair<String, String>> {
val mm = mutableListOf<Pair<String, String>>()
// normal variables
for (variable in st.allVariables()) {
val location = allocations.getValue(variable.name)
val value = when(variable.dt) {
DataType.FLOAT -> (variable.onetimeInitializationNumericValue ?: 0.0).toString()
in NumericDatatypes -> (variable.onetimeInitializationNumericValue ?: 0).toHex()
DataType.STR -> {
val encoded = encoding.encodeString(variable.onetimeInitializationStringValue!!.first, variable.onetimeInitializationStringValue!!.second) + listOf(0u)
encoded.joinToString(",") { it.toInt().toHex() }
}
DataType.ARRAY_F -> {
if(variable.onetimeInitializationArrayValue!=null) {
variable.onetimeInitializationArrayValue!!.joinToString(",") { it.number!!.toString() }
} else {
(1..variable.length!!).joinToString(",") { "0" }
}
}
in ArrayDatatypes -> {
if(variable.onetimeInitializationArrayValue!==null) {
variable.onetimeInitializationArrayValue!!.joinToString(",") {
if(it.number!=null)
it.number!!.toHex()
else
"&${it.addressOf!!.joinToString(".")}"
}
} else {
(1..variable.length!!).joinToString(",") { "0" }
}
}
else -> throw InternalCompilerException("weird dt")
}
mm.add(Pair(variable.name, "@$location ${getTypeString(variable)} $value"))
}
// memory mapped variables
for (variable in st.allMemMappedVariables()) {
val value = when(variable.dt) {
DataType.FLOAT -> "0.0"
in NumericDatatypes -> "0"
DataType.ARRAY_F -> (1..variable.length!!).joinToString(",") { "0.0" }
in ArrayDatatypes -> (1..variable.length!!).joinToString(",") { "0" }
else -> throw InternalCompilerException("weird dt for mem mapped var")
}
mm.add(Pair(variable.name, "@${variable.address} ${getTypeString(variable)} $value"))
}
// memory slabs.
for(slab in st.allMemorySlabs()) {
val address = allocations.getValue(slab.name)
mm.add(Pair(slab.name, "@$address ubyte[${slab.size}] 0"))
}
return mm
}
}

View File

@ -1,3 +1,5 @@
import io.kotest.assertions.throwables.shouldThrow
import io.kotest.assertions.throwables.shouldThrowWithMessage
import io.kotest.core.spec.style.FunSpec
import io.kotest.matchers.collections.shouldBeEmpty
import io.kotest.matchers.shouldBe
@ -6,6 +8,7 @@ import prog8.code.target.VMTarget
import prog8.intermediate.*
import prog8.vm.VirtualMachine
import prog8.vm.VmRunner
import java.lang.IllegalArgumentException
class TestVm: FunSpec( {
@ -39,9 +42,10 @@ class TestVm: FunSpec( {
test("vm execution: modify memory") {
val program = IRProgram("test", IRSymbolTable(null), getTestOptions(), VMTarget())
val block = IRBlock("main", null, IRBlock.BlockAlignment.NONE, Position.DUMMY)
val startSub = IRSubroutine("main.start2222", emptyList(), null, Position.DUMMY) // TODO proper name main.start
val block = IRBlock("testmain", null, IRBlock.BlockAlignment.NONE, Position.DUMMY)
val startSub = IRSubroutine("testmain.testsub", emptyList(), null, Position.DUMMY)
val code = IRCodeChunk(Position.DUMMY)
code += IRInstruction(Opcode.NOP)
code += IRInstruction(Opcode.LOAD, VmDataType.WORD, reg1=1, value=12345)
code += IRInstruction(Opcode.STOREM, VmDataType.WORD, reg1=1, value=1000)
code += IRInstruction(Opcode.RETURN)
@ -59,8 +63,19 @@ class TestVm: FunSpec( {
vm.memory.getUW(1000) shouldBe 12345u
vm.callStack.shouldBeEmpty()
vm.valueStack.shouldBeEmpty()
vm.pc shouldBe 2
vm.stepCount shouldBe 3
vm.pc shouldBe code.lines.size-1
vm.stepCount shouldBe code.lines.size
}
test("vm asmsub not supported") {
val program = IRProgram("test", IRSymbolTable(null), getTestOptions(), VMTarget())
val block = IRBlock("main", null, IRBlock.BlockAlignment.NONE, Position.DUMMY)
val startSub = IRAsmSubroutine("main.asmstart", Position.DUMMY, 0x2000u, emptySet(), emptyList(), emptyList(), "inlined asm here")
block += startSub
program.addBlock(block)
shouldThrowWithMessage<IllegalArgumentException>("vm currently does not support asmsubs: main.asmstart") {
VirtualMachine(program)
}
}
test("vmrunner") {