mirror of
https://github.com/irmen/prog8.git
synced 2024-11-29 01:49:22 +00:00
vm: new translation of IRProgram into vm program list
This commit is contained in:
parent
11c000f764
commit
db1aa8fcbd
@ -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
|
||||
}
|
||||
}
|
||||
})
|
@ -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.
|
||||
|
||||
...
|
||||
|
@ -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
|
||||
|
@ -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,23 +74,24 @@ 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
|
||||
}
|
||||
|
@ -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() {
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
block.asmSubroutines.forEach {
|
||||
// TODO add asmsub to program
|
||||
}
|
||||
}
|
||||
return emptyArray()
|
||||
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()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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") {
|
||||
|
Loading…
Reference in New Issue
Block a user