2022-09-26 17:03:54 +00:00
|
|
|
package prog8.vm
|
|
|
|
|
2023-02-20 22:29:16 +00:00
|
|
|
import prog8.code.core.ArrayDatatypes
|
|
|
|
import prog8.code.core.AssemblyError
|
|
|
|
import prog8.code.core.DataType
|
2022-09-26 17:03:54 +00:00
|
|
|
import prog8.intermediate.*
|
|
|
|
|
|
|
|
class VmProgramLoader {
|
2022-10-16 16:30:14 +00:00
|
|
|
private val placeholders = mutableMapOf<Pair<IRCodeChunk, Int>, String>() // program chunk+index to symbolname
|
2022-09-26 17:03:54 +00:00
|
|
|
|
2022-10-16 16:30:14 +00:00
|
|
|
fun load(irProgram: IRProgram, memory: Memory): List<IRCodeChunk> {
|
2022-09-26 23:50:00 +00:00
|
|
|
placeholders.clear()
|
2022-10-16 16:30:14 +00:00
|
|
|
irProgram.validate()
|
2022-09-26 17:03:54 +00:00
|
|
|
val allocations = VmVariableAllocator(irProgram.st, irProgram.encoding, irProgram.options.compTarget)
|
2022-10-23 15:29:03 +00:00
|
|
|
val variableAddresses = allocations.allocations.toMutableMap()
|
2022-10-16 16:30:14 +00:00
|
|
|
val programChunks = mutableListOf<IRCodeChunk>()
|
2022-09-26 17:03:54 +00:00
|
|
|
|
2022-10-23 15:29:03 +00:00
|
|
|
varsToMemory(irProgram, allocations, variableAddresses, memory)
|
2022-09-27 00:42:01 +00:00
|
|
|
|
2023-02-20 22:29:16 +00:00
|
|
|
if(irProgram.globalInits.isNotEmpty())
|
|
|
|
programChunks += irProgram.globalInits
|
2022-09-26 17:03:54 +00:00
|
|
|
|
|
|
|
// make sure that if there is a "main.start" entrypoint, we jump to it
|
|
|
|
irProgram.blocks.firstOrNull()?.let {
|
2022-11-22 01:04:24 +00:00
|
|
|
if(it.children.any { sub -> sub is IRSubroutine && sub.label=="main.start" }) {
|
2023-03-12 15:09:55 +00:00
|
|
|
val previous = programChunks.lastOrNull()
|
|
|
|
val chunk = IRCodeChunk(null, previous)
|
2022-10-16 16:30:14 +00:00
|
|
|
placeholders[Pair(chunk, 0)] = "main.start"
|
|
|
|
chunk += IRInstruction(Opcode.JUMP, labelSymbol = "main.start")
|
2023-03-12 15:09:55 +00:00
|
|
|
previous?.let { p -> p.next = chunk }
|
2022-10-16 16:30:14 +00:00
|
|
|
programChunks += chunk
|
2022-09-26 17:03:54 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-10-23 15:29:03 +00:00
|
|
|
// load rest of the program into the list
|
2022-10-23 23:57:37 +00:00
|
|
|
val chunkReplacements = mutableListOf<Pair<IRCodeChunkBase, IRCodeChunk>>()
|
2022-09-26 17:03:54 +00:00
|
|
|
irProgram.blocks.forEach { block ->
|
|
|
|
if(block.address!=null)
|
2022-09-27 01:32:39 +00:00
|
|
|
throw IRParseException("blocks cannot have a load address for vm: ${block.name}")
|
2022-09-26 17:03:54 +00:00
|
|
|
|
2022-11-22 01:04:24 +00:00
|
|
|
block.children.forEach { child ->
|
|
|
|
when(child) {
|
2023-01-26 00:38:13 +00:00
|
|
|
is IRAsmSubroutine -> throw IRParseException("vm does not support asmsubs (use normal sub): ${child.label}")
|
2022-11-22 01:04:24 +00:00
|
|
|
is IRCodeChunk -> programChunks += child
|
|
|
|
is IRInlineAsmChunk -> {
|
|
|
|
val replacement = addAssemblyToProgram(child, programChunks, variableAddresses)
|
|
|
|
chunkReplacements += replacement
|
|
|
|
}
|
|
|
|
is IRInlineBinaryChunk -> throw IRParseException("inline binary data not yet supported in the VM") // TODO
|
|
|
|
is IRSubroutine -> {
|
|
|
|
child.chunks.forEach { chunk ->
|
|
|
|
when (chunk) {
|
|
|
|
is IRInlineAsmChunk -> {
|
|
|
|
val replacement = addAssemblyToProgram(chunk, programChunks, variableAddresses)
|
|
|
|
chunkReplacements += replacement
|
|
|
|
}
|
|
|
|
is IRInlineBinaryChunk -> throw IRParseException("inline binary data not yet supported in the VM") // TODO
|
|
|
|
is IRCodeChunk -> programChunks += chunk
|
|
|
|
else -> throw AssemblyError("weird chunk type")
|
|
|
|
}
|
|
|
|
} }
|
2022-09-26 23:50:00 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-10-23 23:57:37 +00:00
|
|
|
pass2translateSyscalls(programChunks)
|
2022-10-23 15:29:03 +00:00
|
|
|
pass2replaceLabelsByProgIndex(programChunks, variableAddresses)
|
2022-10-23 23:57:37 +00:00
|
|
|
phase2relinkReplacedChunks(chunkReplacements, programChunks)
|
|
|
|
|
|
|
|
programChunks.forEach {
|
|
|
|
it.instructions.forEach { ins ->
|
2023-04-07 20:56:23 +00:00
|
|
|
if (ins.labelSymbol != null && ins.opcode !in OpcodesThatBranch && ins.opcode !in OpcodesForCpuRegisters)
|
2022-10-23 23:57:37 +00:00
|
|
|
require(ins.value != null) { "instruction with labelSymbol for a var should have value set to var's memory address" }
|
|
|
|
}
|
|
|
|
}
|
2022-09-26 23:50:00 +00:00
|
|
|
|
2022-10-16 16:30:14 +00:00
|
|
|
return programChunks
|
|
|
|
}
|
|
|
|
|
2022-10-23 23:57:37 +00:00
|
|
|
private fun phase2relinkReplacedChunks(
|
|
|
|
replacements: MutableList<Pair<IRCodeChunkBase, IRCodeChunk>>,
|
|
|
|
programChunks: MutableList<IRCodeChunk>
|
|
|
|
) {
|
|
|
|
replacements.forEach { (old, new) ->
|
|
|
|
programChunks.forEach { chunk ->
|
|
|
|
if(chunk.next === old) {
|
|
|
|
chunk.next = new
|
|
|
|
}
|
|
|
|
chunk.instructions.forEach { ins ->
|
|
|
|
if(ins.branchTarget === old) {
|
|
|
|
ins.branchTarget = new
|
|
|
|
} else if(ins.branchTarget==null && ins.labelSymbol==new.label) {
|
|
|
|
ins.branchTarget = new
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private fun pass2translateSyscalls(chunks: MutableList<IRCodeChunk>) {
|
|
|
|
chunks.forEach { chunk ->
|
|
|
|
chunk.instructions.withIndex().forEach { (index, ins) ->
|
|
|
|
if(ins.opcode == Opcode.SYSCALL) {
|
|
|
|
// convert IR Syscall to VM Syscall
|
|
|
|
val vmSyscall = when(ins.value!!) {
|
|
|
|
IMSyscall.SORT_UBYTE.number -> Syscall.SORT_UBYTE
|
|
|
|
IMSyscall.SORT_BYTE.number -> Syscall.SORT_BYTE
|
|
|
|
IMSyscall.SORT_UWORD.number -> Syscall.SORT_UWORD
|
|
|
|
IMSyscall.SORT_WORD.number -> Syscall.SORT_WORD
|
|
|
|
IMSyscall.ANY_BYTE.number -> Syscall.ANY_BYTE
|
|
|
|
IMSyscall.ANY_WORD.number -> Syscall.ANY_WORD
|
|
|
|
IMSyscall.ANY_FLOAT.number -> Syscall.ANY_FLOAT
|
|
|
|
IMSyscall.ALL_BYTE.number -> Syscall.ALL_BYTE
|
|
|
|
IMSyscall.ALL_WORD.number -> Syscall.ALL_WORD
|
|
|
|
IMSyscall.ALL_FLOAT.number -> Syscall.ALL_FLOAT
|
|
|
|
IMSyscall.REVERSE_BYTES.number -> Syscall.REVERSE_BYTES
|
|
|
|
IMSyscall.REVERSE_WORDS.number -> Syscall.REVERSE_WORDS
|
|
|
|
IMSyscall.REVERSE_FLOATS.number -> Syscall.REVERSE_FLOATS
|
2022-11-04 21:37:42 +00:00
|
|
|
IMSyscall.COMPARE_STRINGS.number -> Syscall.COMPARE_STRINGS
|
|
|
|
IMSyscall.STRING_CONTAINS.number -> Syscall.STRING_CONTAINS
|
|
|
|
IMSyscall.BYTEARRAY_CONTAINS.number -> Syscall.BYTEARRAY_CONTAINS
|
|
|
|
IMSyscall.WORDARRAY_CONTAINS.number -> Syscall.WORDARRAY_CONTAINS
|
2022-10-23 23:57:37 +00:00
|
|
|
else -> null
|
|
|
|
}
|
|
|
|
|
|
|
|
if(vmSyscall!=null)
|
|
|
|
chunk.instructions[index] = ins.copy(value = vmSyscall.ordinal)
|
|
|
|
}
|
|
|
|
|
|
|
|
val label = ins.labelSymbol
|
|
|
|
if (label != null && ins.opcode !in OpcodesThatBranch) {
|
|
|
|
placeholders[Pair(chunk, index)] = label
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-10-16 16:30:14 +00:00
|
|
|
private fun pass2replaceLabelsByProgIndex(
|
|
|
|
chunks: MutableList<IRCodeChunk>,
|
2022-10-23 15:29:03 +00:00
|
|
|
variableAddresses: MutableMap<String, Int>
|
2022-10-16 16:30:14 +00:00
|
|
|
) {
|
|
|
|
for((ref, label) in placeholders) {
|
|
|
|
val (chunk, line) = ref
|
2022-10-23 15:29:03 +00:00
|
|
|
val replacement = variableAddresses[label]
|
2022-10-16 16:30:14 +00:00
|
|
|
if(replacement==null) {
|
|
|
|
// it could be an address + index: symbol+42
|
|
|
|
if('+' in label) {
|
|
|
|
val (symbol, indexStr) = label.split('+')
|
|
|
|
val index = indexStr.toInt()
|
2022-10-23 15:29:03 +00:00
|
|
|
val address = variableAddresses.getValue(symbol) + index
|
2022-10-16 16:30:14 +00:00
|
|
|
chunk.instructions[line] = chunk.instructions[line].copy(value = address)
|
|
|
|
} else {
|
2022-10-23 15:29:03 +00:00
|
|
|
// placeholder is not a variable, so it must be a label of a code chunk instead
|
|
|
|
val target: IRCodeChunk? = chunks.firstOrNull { it.label==label }
|
2022-11-12 12:45:02 +00:00
|
|
|
val opcode = chunk.instructions[line].opcode
|
2023-04-07 20:56:23 +00:00
|
|
|
if(target==null) {
|
|
|
|
// exception allowed: storecpu/loadcpu instructions that refer to CPU registers
|
|
|
|
if(opcode !in OpcodesForCpuRegisters)
|
|
|
|
throw IRParseException("placeholder not found in variables nor labels: $label")
|
|
|
|
}
|
2022-11-12 12:45:02 +00:00
|
|
|
else if(opcode in OpcodesThatBranch) {
|
2022-10-23 15:29:03 +00:00
|
|
|
chunk.instructions[line] = chunk.instructions[line].copy(branchTarget = target, value = null)
|
2022-11-12 12:45:02 +00:00
|
|
|
} else if(opcode in OpcodesWithMemoryAddressAsValue) {
|
|
|
|
throw IRParseException("vm cannot yet load a label address as a value: ${chunk.instructions[line]}") // TODO
|
|
|
|
} else {
|
|
|
|
throw IRParseException("vm cannot yet load a label address as a value: ${chunk.instructions[line]}") // TODO
|
2022-10-23 15:29:03 +00:00
|
|
|
}
|
2022-10-16 16:30:14 +00:00
|
|
|
}
|
|
|
|
} else {
|
|
|
|
chunk.instructions[line] = chunk.instructions[line].copy(value = replacement)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-09-27 00:42:01 +00:00
|
|
|
private fun varsToMemory(
|
|
|
|
program: IRProgram,
|
|
|
|
allocations: VmVariableAllocator,
|
|
|
|
symbolAddresses: MutableMap<String, Int>,
|
|
|
|
memory: Memory
|
|
|
|
) {
|
|
|
|
program.st.allVariables().forEach { variable ->
|
|
|
|
var addr = allocations.allocations.getValue(variable.name)
|
2022-12-03 16:46:06 +00:00
|
|
|
|
2023-02-19 02:07:55 +00:00
|
|
|
// zero out uninitialized variables.
|
2023-02-19 14:23:57 +00:00
|
|
|
if(variable.uninitialized) {
|
2022-12-04 15:02:58 +00:00
|
|
|
if(variable.dt in ArrayDatatypes) {
|
|
|
|
repeat(variable.length!!) {
|
|
|
|
when(variable.dt) {
|
|
|
|
DataType.STR, DataType.ARRAY_UB, DataType.ARRAY_B, DataType.ARRAY_BOOL -> {
|
|
|
|
memory.setUB(addr, 0u)
|
|
|
|
addr++
|
|
|
|
}
|
|
|
|
DataType.ARRAY_UW, DataType.ARRAY_W -> {
|
|
|
|
memory.setUW(addr, 0u)
|
|
|
|
addr += 2
|
|
|
|
}
|
|
|
|
DataType.ARRAY_F -> {
|
|
|
|
memory.setFloat(addr, 0.0f)
|
|
|
|
addr += program.options.compTarget.machine.FLOAT_MEM_SIZE
|
|
|
|
}
|
|
|
|
else -> throw IRParseException("invalid dt")
|
2022-12-03 16:46:06 +00:00
|
|
|
}
|
2022-12-04 15:02:58 +00:00
|
|
|
}
|
2022-12-03 16:46:06 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-09-27 00:42:01 +00:00
|
|
|
variable.onetimeInitializationNumericValue?.let {
|
|
|
|
when(variable.dt) {
|
|
|
|
DataType.UBYTE -> memory.setUB(addr, it.toInt().toUByte())
|
|
|
|
DataType.BYTE -> memory.setSB(addr, it.toInt().toByte())
|
|
|
|
DataType.UWORD -> memory.setUW(addr, it.toInt().toUShort())
|
|
|
|
DataType.WORD -> memory.setSW(addr, it.toInt().toShort())
|
|
|
|
DataType.FLOAT -> memory.setFloat(addr, it.toFloat())
|
2022-09-27 01:32:39 +00:00
|
|
|
else -> throw IRParseException("invalid dt")
|
2022-09-27 00:42:01 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
variable.onetimeInitializationArrayValue?.let {
|
2022-09-28 13:58:58 +00:00
|
|
|
require(variable.length==it.size || it.size==1 || it.size==0)
|
|
|
|
if(it.isEmpty() || it.size==1) {
|
|
|
|
val value = if(it.isEmpty()) {
|
2023-02-19 14:23:57 +00:00
|
|
|
require(variable.uninitialized)
|
2022-09-28 13:58:58 +00:00
|
|
|
0.0
|
|
|
|
} else {
|
2023-02-19 14:23:57 +00:00
|
|
|
require(!variable.uninitialized)
|
2022-09-28 13:58:58 +00:00
|
|
|
it[0].number!!
|
|
|
|
}
|
2022-09-27 00:42:01 +00:00
|
|
|
when(variable.dt) {
|
|
|
|
DataType.STR, DataType.ARRAY_UB -> {
|
|
|
|
repeat(variable.length!!) {
|
|
|
|
memory.setUB(addr, value.toInt().toUByte())
|
|
|
|
addr++
|
|
|
|
}
|
|
|
|
}
|
|
|
|
DataType.ARRAY_B -> {
|
|
|
|
repeat(variable.length!!) {
|
|
|
|
memory.setSB(addr, value.toInt().toByte())
|
|
|
|
addr++
|
|
|
|
}
|
|
|
|
}
|
|
|
|
DataType.ARRAY_UW -> {
|
|
|
|
repeat(variable.length!!) {
|
|
|
|
memory.setUW(addr, value.toInt().toUShort())
|
|
|
|
addr+=2
|
|
|
|
}
|
|
|
|
}
|
|
|
|
DataType.ARRAY_W -> {
|
|
|
|
repeat(variable.length!!) {
|
|
|
|
memory.setSW(addr, value.toInt().toShort())
|
|
|
|
addr+=2
|
|
|
|
}
|
|
|
|
}
|
|
|
|
DataType.ARRAY_F -> {
|
|
|
|
repeat(variable.length!!) {
|
|
|
|
memory.setFloat(addr, value.toFloat())
|
|
|
|
addr += program.options.compTarget.machine.FLOAT_MEM_SIZE
|
|
|
|
}
|
|
|
|
}
|
2022-09-27 01:32:39 +00:00
|
|
|
else -> throw IRParseException("invalid dt")
|
2022-09-27 00:42:01 +00:00
|
|
|
}
|
|
|
|
} else {
|
|
|
|
when(variable.dt) {
|
|
|
|
DataType.STR, DataType.ARRAY_UB -> {
|
|
|
|
for(elt in it) {
|
|
|
|
memory.setUB(addr, elt.number!!.toInt().toUByte())
|
|
|
|
addr++
|
|
|
|
}
|
|
|
|
}
|
|
|
|
DataType.ARRAY_B -> {
|
|
|
|
for(elt in it) {
|
|
|
|
memory.setSB(addr, elt.number!!.toInt().toByte())
|
|
|
|
addr++
|
|
|
|
}
|
|
|
|
}
|
|
|
|
DataType.ARRAY_UW -> {
|
|
|
|
for(elt in it) {
|
2022-12-30 17:07:53 +00:00
|
|
|
if(elt.addressOfSymbol!=null) {
|
|
|
|
val name = elt.addressOfSymbol!!
|
2022-11-12 12:45:02 +00:00
|
|
|
val symbolAddress = symbolAddresses[name]
|
2022-12-30 17:07:53 +00:00
|
|
|
?: throw IRParseException("vm cannot yet load a label address as a value: $name") // TODO
|
2022-09-27 00:42:01 +00:00
|
|
|
memory.setUW(addr, symbolAddress.toUShort())
|
|
|
|
} else {
|
|
|
|
memory.setUW(addr, elt.number!!.toInt().toUShort())
|
|
|
|
}
|
|
|
|
addr+=2
|
|
|
|
}
|
|
|
|
}
|
|
|
|
DataType.ARRAY_W -> {
|
|
|
|
for(elt in it) {
|
|
|
|
memory.setSW(addr, elt.number!!.toInt().toShort())
|
|
|
|
addr+=2
|
|
|
|
}
|
|
|
|
}
|
|
|
|
DataType.ARRAY_F -> {
|
|
|
|
for(elt in it) {
|
2023-04-03 22:06:55 +00:00
|
|
|
memory.setFloat(addr, elt.number!!.toFloat())
|
2022-09-27 00:42:01 +00:00
|
|
|
addr+=program.options.compTarget.machine.FLOAT_MEM_SIZE
|
|
|
|
}
|
|
|
|
}
|
2022-09-27 01:32:39 +00:00
|
|
|
else -> throw IRParseException("invalid dt")
|
2022-09-27 00:42:01 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
require(variable.onetimeInitializationStringValue==null) { "in vm/ir, strings should have been converted into bytearrays." }
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-09-26 23:50:00 +00:00
|
|
|
|
|
|
|
private fun addAssemblyToProgram(
|
|
|
|
asmChunk: IRInlineAsmChunk,
|
2022-10-16 16:30:14 +00:00
|
|
|
chunks: MutableList<IRCodeChunk>,
|
2022-09-27 15:45:26 +00:00
|
|
|
symbolAddresses: MutableMap<String, Int>,
|
2022-10-23 23:57:37 +00:00
|
|
|
): Pair<IRCodeChunkBase, IRCodeChunk> {
|
2022-09-30 12:05:11 +00:00
|
|
|
if(asmChunk.isIR) {
|
2022-11-02 21:12:42 +00:00
|
|
|
val chunk = IRCodeChunk(asmChunk.label, asmChunk.next)
|
2022-09-30 12:05:11 +00:00
|
|
|
asmChunk.assembly.lineSequence().forEach {
|
2022-10-16 16:30:14 +00:00
|
|
|
val parsed = parseIRCodeLine(it.trim(), Pair(chunk, chunk.instructions.size), placeholders)
|
|
|
|
parsed.fold(
|
|
|
|
ifLeft = { instruction -> chunk += instruction },
|
|
|
|
ifRight = { label -> symbolAddresses[label] = chunk.instructions.size }
|
|
|
|
)
|
2022-09-30 12:05:11 +00:00
|
|
|
}
|
2022-10-16 16:30:14 +00:00
|
|
|
chunks += chunk
|
2022-10-23 23:57:37 +00:00
|
|
|
return Pair(asmChunk, chunk)
|
2022-09-30 12:05:11 +00:00
|
|
|
} else {
|
2022-11-02 21:12:42 +00:00
|
|
|
throw IRParseException("vm currently does not support real inlined assembly (only IR): ${asmChunk.label}")
|
2022-09-26 17:03:54 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|