prog8/virtualmachine/src/prog8/vm/VmProgramLoader.kt

251 lines
11 KiB
Kotlin
Raw Normal View History

2022-09-26 17:03:54 +00:00
package prog8.vm
import prog8.code.core.AssemblyError
import prog8.code.core.DataType
2022-10-16 16:30:14 +00:00
import prog8.code.core.Position
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> {
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)
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
varsToMemory(irProgram, allocations, variableAddresses, memory)
2022-10-16 16:30:14 +00:00
if(!irProgram.options.dontReinitGlobals) {
if(irProgram.globalInits.isNotEmpty())
programChunks += irProgram.globalInits
2022-10-16 16:30:14 +00:00
}
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 {
if(it.subroutines.any { sub -> sub.name=="main.start" }) {
2022-10-16 16:30:14 +00:00
val chunk = IRCodeChunk(null, Position.DUMMY, null)
placeholders[Pair(chunk, 0)] = "main.start"
chunk += IRInstruction(Opcode.JUMP, labelSymbol = "main.start")
programChunks += chunk
2022-09-26 17:03:54 +00:00
}
}
// load rest of the program into the list
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
block.inlineAssembly.forEach { addAssemblyToProgram(it, programChunks, variableAddresses) }
2022-09-26 17:03:54 +00:00
block.subroutines.forEach {
it.chunks.forEach { chunk ->
2022-10-03 22:47:51 +00:00
when (chunk) {
is IRInlineAsmChunk -> addAssemblyToProgram(chunk, programChunks, variableAddresses)
2022-10-16 16:30:14 +00:00
is IRInlineBinaryChunk -> TODO("inline binary data not yet supported in the VM")
is IRCodeChunk -> programChunks += chunk
else -> throw AssemblyError("weird chunk type")
2022-10-03 22:47:51 +00:00
}
}
}
if(block.asmSubroutines.any())
2022-09-27 01:32:39 +00:00
throw IRParseException("vm currently does not support asmsubs: ${block.asmSubroutines.first().name}")
}
pass2replaceLabelsByProgIndex(programChunks, variableAddresses)
2022-10-16 16:30:14 +00:00
return programChunks
}
private fun pass2replaceLabelsByProgIndex(
chunks: MutableList<IRCodeChunk>,
variableAddresses: MutableMap<String, Int>
2022-10-16 16:30:14 +00:00
) {
for((ref, label) in placeholders) {
val (chunk, line) = ref
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()
val address = variableAddresses.getValue(symbol) + index
2022-10-16 16:30:14 +00:00
chunk.instructions[line] = chunk.instructions[line].copy(value = address)
} else {
// placeholder is not a variable, so it must be a label of a code chunk instead
val target: IRCodeChunk? = chunks.firstOrNull { it.label==label }
if(target==null)
throw IRParseException("placeholder not found in variables nor labels: $label")
else {
require(chunk.instructions[line].opcode in OpcodesThatBranch)
chunk.instructions[line] = chunk.instructions[line].copy(branchTarget = target, value = null)
}
2022-10-16 16:30:14 +00:00
}
} else {
chunk.instructions[line] = chunk.instructions[line].copy(value = replacement)
}
}
}
// TODO replace IM syscalls by their VM Syscall equivalent
// private fun addToProgram(
// instructions: Iterable<IRInstruction>,
// program: MutableList<IRCodeChunk>
// ) {
// val chunk = IRCodeChunk(null, Position.DUMMY, null)
// instructions.map {
// it.labelSymbol?.let { symbol -> placeholders[Pair(chunk, chunk.instructions.size)]=symbol }
// if(it.opcode==Opcode.SYSCALL) {
// // convert IR Syscall to VM Syscall
// val vmSyscall = when(it.value!!) {
// IMSyscall.SORT_UBYTE.ordinal -> Syscall.SORT_UBYTE
// IMSyscall.SORT_BYTE.ordinal -> Syscall.SORT_BYTE
// IMSyscall.SORT_UWORD.ordinal -> Syscall.SORT_UWORD
// IMSyscall.SORT_WORD.ordinal -> Syscall.SORT_WORD
// IMSyscall.ANY_BYTE.ordinal -> Syscall.ANY_BYTE
// IMSyscall.ANY_WORD.ordinal -> Syscall.ANY_WORD
// IMSyscall.ANY_FLOAT.ordinal -> Syscall.ANY_FLOAT
// IMSyscall.ALL_BYTE.ordinal -> Syscall.ALL_BYTE
// IMSyscall.ALL_WORD.ordinal -> Syscall.ALL_WORD
// IMSyscall.ALL_FLOAT.ordinal -> Syscall.ALL_FLOAT
// IMSyscall.REVERSE_BYTES.ordinal -> Syscall.REVERSE_BYTES
// IMSyscall.REVERSE_WORDS.ordinal -> Syscall.REVERSE_WORDS
// IMSyscall.REVERSE_FLOATS.ordinal -> Syscall.REVERSE_FLOATS
// else -> throw IRParseException("invalid IM syscall number $it")
// }
// chunk += it.copy(value=vmSyscall.ordinal)
// } else {
// chunk += it
// }
// }
// program += chunk
// }
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)
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")
}
}
variable.onetimeInitializationArrayValue?.let {
require(variable.length==it.size || it.size==1 || it.size==0)
if(it.isEmpty() || it.size==1) {
val value = if(it.isEmpty()) {
require(variable.bss)
0.0
} else {
require(!variable.bss)
it[0].number!!
}
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")
}
} 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) {
if(elt.addressOf!=null) {
val symbolAddress = symbolAddresses.getValue(elt.addressOf!!.joinToString("."))
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) {
memory.setSW(addr, elt.number!!.toInt().toShort())
addr+=program.options.compTarget.machine.FLOAT_MEM_SIZE
}
}
2022-09-27 01:32:39 +00:00
else -> throw IRParseException("invalid dt")
}
}
}
require(variable.onetimeInitializationStringValue==null) { "in vm/ir, strings should have been converted into bytearrays." }
}
}
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>,
) {
if(asmChunk.isIR) {
2022-10-16 16:30:14 +00:00
val chunk = IRCodeChunk(asmChunk.label, asmChunk.position, null)
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-10-16 16:30:14 +00:00
chunks += chunk
} else {
throw IRParseException("vm currently does not support real inlined assembly (only IR): ${asmChunk.position}")
2022-09-26 17:03:54 +00:00
}
}
}