mirror of
https://github.com/irmen/prog8.git
synced 2025-08-09 19:25:22 +00:00
vm: get rid of .p8virt file and cruft
This commit is contained in:
@@ -4,12 +4,11 @@ import prog8.code.core.CompilationOptions
|
|||||||
import prog8.code.core.CpuType
|
import prog8.code.core.CpuType
|
||||||
import prog8.code.core.IMachineDefinition
|
import prog8.code.core.IMachineDefinition
|
||||||
import prog8.code.core.Zeropage
|
import prog8.code.core.Zeropage
|
||||||
import java.io.File
|
|
||||||
import java.nio.file.Path
|
import java.nio.file.Path
|
||||||
|
import kotlin.io.path.isReadable
|
||||||
import kotlin.io.path.name
|
import kotlin.io.path.name
|
||||||
import kotlin.io.path.readText
|
import kotlin.io.path.readText
|
||||||
|
|
||||||
|
|
||||||
class VirtualMachineDefinition: IMachineDefinition {
|
class VirtualMachineDefinition: IMachineDefinition {
|
||||||
|
|
||||||
override val cpu = CpuType.VIRTUAL
|
override val cpu = CpuType.VIRTUAL
|
||||||
@@ -35,14 +34,15 @@ class VirtualMachineDefinition: IMachineDefinition {
|
|||||||
// to not have external module dependencies in our own module, we launch the virtual machine via reflection
|
// to not have external module dependencies in our own module, we launch the virtual machine via reflection
|
||||||
val vm = Class.forName("prog8.vm.VmRunner").getDeclaredConstructor().newInstance() as IVirtualMachineRunner
|
val vm = Class.forName("prog8.vm.VmRunner").getDeclaredConstructor().newInstance() as IVirtualMachineRunner
|
||||||
val filename = programNameWithPath.name
|
val filename = programNameWithPath.name
|
||||||
if(filename.endsWith(".p8virt")) {
|
if(programNameWithPath.isReadable()) {
|
||||||
vm.runProgram(programNameWithPath.readText())
|
vm.runProgram(programNameWithPath.readText())
|
||||||
} else if(File("$filename.p8virt").isFile) {
|
} else {
|
||||||
val source = File("$filename.p8virt").readText()
|
val withExt = programNameWithPath.resolveSibling("$filename.p8ir")
|
||||||
vm.runProgram(source)
|
if(withExt.isReadable())
|
||||||
|
vm.runProgram(withExt.readText())
|
||||||
|
else
|
||||||
|
throw NoSuchFileException(withExt.toFile(), reason="not a .p8ir file")
|
||||||
}
|
}
|
||||||
else
|
|
||||||
throw IllegalArgumentException("vm can only run .p8virt or .p8ir files")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun isIOAddress(address: UInt): Boolean = false
|
override fun isIOAddress(address: UInt): Boolean = false
|
||||||
@@ -53,5 +53,5 @@ class VirtualMachineDefinition: IMachineDefinition {
|
|||||||
}
|
}
|
||||||
|
|
||||||
interface IVirtualMachineRunner {
|
interface IVirtualMachineRunner {
|
||||||
fun runProgram(source: String)
|
fun runProgram(irSource: CharSequence)
|
||||||
}
|
}
|
||||||
|
@@ -65,7 +65,7 @@ class IRCodeGen(
|
|||||||
irProg.blocks.asSequence().flatMap { it.subroutines }.flatMap { it.chunks }.forEach { chunk ->
|
irProg.blocks.asSequence().flatMap { it.subroutines }.flatMap { it.chunks }.forEach { chunk ->
|
||||||
chunk.lines.withIndex().forEach {
|
chunk.lines.withIndex().forEach {
|
||||||
(lineIndex, line)-> if(line is IRCodeInstruction) {
|
(lineIndex, line)-> if(line is IRCodeInstruction) {
|
||||||
val symbolExpr = line.ins.labelSymbol?.single()
|
val symbolExpr = line.ins.labelSymbol
|
||||||
if(symbolExpr!=null) {
|
if(symbolExpr!=null) {
|
||||||
val symbol: String
|
val symbol: String
|
||||||
val index: UInt
|
val index: UInt
|
||||||
|
@@ -84,7 +84,7 @@ internal class IRPeepholeOptimizer(private val irprog: IRProgram) {
|
|||||||
// if jumping to label immediately following this
|
// if jumping to label immediately following this
|
||||||
if(idx < chunk.lines.size-1) {
|
if(idx < chunk.lines.size-1) {
|
||||||
val label = chunk.lines[idx+1] as? IRCodeLabel
|
val label = chunk.lines[idx+1] as? IRCodeLabel
|
||||||
if(labelSymbol.size==1 && label?.name == labelSymbol[0]) {
|
if(label?.name == labelSymbol) {
|
||||||
chunk.lines.removeAt(idx)
|
chunk.lines.removeAt(idx)
|
||||||
changed = true
|
changed = true
|
||||||
}
|
}
|
||||||
|
@@ -1,125 +1,16 @@
|
|||||||
package prog8.codegen.virtual
|
package prog8.codegen.virtual
|
||||||
|
|
||||||
import prog8.code.core.AssemblyError
|
|
||||||
import prog8.code.core.CompilationOptions
|
import prog8.code.core.CompilationOptions
|
||||||
import prog8.code.core.IAssemblyProgram
|
import prog8.code.core.IAssemblyProgram
|
||||||
import prog8.intermediate.*
|
import prog8.intermediate.IRFileWriter
|
||||||
import prog8.vm.Syscall
|
import prog8.intermediate.IRProgram
|
||||||
import java.io.BufferedWriter
|
|
||||||
import kotlin.io.path.bufferedWriter
|
|
||||||
import kotlin.io.path.div
|
|
||||||
|
|
||||||
internal class VmAssemblyProgram(override val name: String, private val irProgram: IRProgram): IAssemblyProgram {
|
internal class VmAssemblyProgram(override val name: String, private val irProgram: IRProgram): IAssemblyProgram {
|
||||||
|
|
||||||
override fun assemble(dummyOptions: CompilationOptions): Boolean {
|
override fun assemble(options: CompilationOptions): Boolean {
|
||||||
val outfile = irProgram.options.outputDir / ("$name.p8virt")
|
val writtenFile = IRFileWriter(irProgram, null).write()
|
||||||
println("write code to $outfile")
|
println("Wrote intermediate representation to $writtenFile")
|
||||||
|
|
||||||
// at last, allocate the variables in memory.
|
|
||||||
val allocations = VmVariableAllocator(irProgram.st, irProgram.encoding, irProgram.options.compTarget)
|
|
||||||
|
|
||||||
outfile.bufferedWriter().use { out ->
|
|
||||||
allocations.asVmMemory().forEach { (name, alloc) ->
|
|
||||||
out.write("var ${name} $alloc\n")
|
|
||||||
}
|
|
||||||
|
|
||||||
out.write("------PROGRAM------\n")
|
|
||||||
|
|
||||||
if(!irProgram.options.dontReinitGlobals) {
|
|
||||||
out.write("; global var inits\n")
|
|
||||||
irProgram.globalInits.forEach { out.writeLine(it) }
|
|
||||||
}
|
|
||||||
irProgram.blocks.firstOrNull()?.let {
|
|
||||||
if(it.subroutines.any { it.name=="main.start" }) {
|
|
||||||
// there is a "main.start" entrypoint, jump to it
|
|
||||||
out.writeLine(IRCodeInstruction(Opcode.JUMP, labelSymbol = "main.start"))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
out.write("; actual program code\n")
|
|
||||||
|
|
||||||
irProgram.blocks.forEach { block ->
|
|
||||||
if(block.address!=null)
|
|
||||||
TODO("blocks can't have a load address for vm")
|
|
||||||
out.write("; BLOCK ${block.name} ${block.position}\n")
|
|
||||||
block.inlineAssembly.forEach { asm ->
|
|
||||||
out.write("; ASM ${asm.position}\n")
|
|
||||||
out.write(asm.assembly)
|
|
||||||
out.write("\n")
|
|
||||||
}
|
|
||||||
block.subroutines.forEach { sub ->
|
|
||||||
out.write("; SUB ${sub.name} ${sub.position}\n")
|
|
||||||
out.write("_${sub.name}:\n")
|
|
||||||
sub.chunks.forEach { chunk ->
|
|
||||||
if(chunk is IRInlineAsmChunk) {
|
|
||||||
out.write("; ASM ${chunk.position}\n")
|
|
||||||
out.write(processInlinedAsm(chunk.assembly, allocations))
|
|
||||||
out.write("\n")
|
|
||||||
} else {
|
|
||||||
chunk.lines.forEach { out.writeLine(it) }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
out.write("; END SUB ${sub.name}\n")
|
|
||||||
}
|
|
||||||
block.asmSubroutines.forEach { sub ->
|
|
||||||
out.write("; ASMSUB ${sub.name} ${sub.position}\n")
|
|
||||||
out.write("_${sub.name}:\n")
|
|
||||||
out.write(processInlinedAsm(sub.assembly, allocations))
|
|
||||||
out.write("\n; END ASMSUB ${sub.name}\n")
|
|
||||||
}
|
|
||||||
out.write("; END BLOCK ${block.name}\n")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun processInlinedAsm(asm: String, allocations: VmVariableAllocator): String {
|
|
||||||
// TODO do we have to replace variable names by their allocated address???
|
|
||||||
return asm
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun BufferedWriter.writeLine(line: IRCodeLine) {
|
|
||||||
when(line) {
|
|
||||||
is IRCodeComment -> {
|
|
||||||
write("; ${line.comment}\n")
|
|
||||||
}
|
|
||||||
is IRCodeInstruction -> {
|
|
||||||
if(line.ins.opcode==Opcode.SYSCALL) {
|
|
||||||
// convert IM Syscall to VM Syscall
|
|
||||||
val vmSyscall = when(line.ins.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 IllegalArgumentException("invalid IM syscall number ${line.ins.value}")
|
|
||||||
}
|
|
||||||
val newIns = line.ins.copy(value = vmSyscall.ordinal)
|
|
||||||
write(newIns.toString() + "\n")
|
|
||||||
} else
|
|
||||||
write(line.ins.toString() + "\n")
|
|
||||||
}
|
|
||||||
is IRCodeInlineBinary -> {
|
|
||||||
write("!binary ")
|
|
||||||
line.data.withIndex().forEach {(index, byte) ->
|
|
||||||
write(byte.toString(16).padStart(2,'0'))
|
|
||||||
if(index and 63 == 63 && index<line.data.size-1)
|
|
||||||
write("\n!binary ")
|
|
||||||
}
|
|
||||||
write("\n")
|
|
||||||
}
|
|
||||||
is IRCodeLabel -> {
|
|
||||||
write("_${line.name}:\n")
|
|
||||||
}
|
|
||||||
else -> throw AssemblyError("invalid IR code line")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@@ -2,18 +2,21 @@ package prog8
|
|||||||
|
|
||||||
import kotlinx.cli.*
|
import kotlinx.cli.*
|
||||||
import prog8.ast.base.AstException
|
import prog8.ast.base.AstException
|
||||||
import prog8.code.core.*
|
import prog8.code.core.CbmPrgLauncherType
|
||||||
|
import prog8.code.core.toHex
|
||||||
import prog8.code.target.*
|
import prog8.code.target.*
|
||||||
import prog8.code.target.virtual.VirtualMachineDefinition
|
import prog8.code.target.virtual.VirtualMachineDefinition
|
||||||
import prog8.codegen.virtual.VmCodeGen
|
|
||||||
import prog8.compiler.CompilationResult
|
import prog8.compiler.CompilationResult
|
||||||
import prog8.compiler.CompilerArguments
|
import prog8.compiler.CompilerArguments
|
||||||
import prog8.compiler.compileProgram
|
import prog8.compiler.compileProgram
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.nio.file.*
|
import java.nio.file.FileSystems
|
||||||
|
import java.nio.file.Path
|
||||||
|
import java.nio.file.StandardWatchEventKinds
|
||||||
|
import java.nio.file.WatchKey
|
||||||
import java.time.LocalDateTime
|
import java.time.LocalDateTime
|
||||||
import kotlin.system.exitProcess
|
|
||||||
import kotlin.io.path.Path
|
import kotlin.io.path.Path
|
||||||
|
import kotlin.system.exitProcess
|
||||||
|
|
||||||
|
|
||||||
fun main(args: Array<String>) {
|
fun main(args: Array<String>) {
|
||||||
@@ -48,7 +51,7 @@ private fun compileMain(args: Array<String>): Boolean {
|
|||||||
val slowCodegenWarnings by cli.option(ArgType.Boolean, fullName = "slowwarn", description="show debug warnings about slow/problematic assembly code generation")
|
val slowCodegenWarnings by cli.option(ArgType.Boolean, fullName = "slowwarn", description="show debug warnings about slow/problematic assembly code generation")
|
||||||
val sourceDirs by cli.option(ArgType.String, fullName="srcdirs", description = "list of extra paths, separated with ${File.pathSeparator}, to search in for imported modules").multiple().delimiter(File.pathSeparator)
|
val sourceDirs by cli.option(ArgType.String, fullName="srcdirs", description = "list of extra paths, separated with ${File.pathSeparator}, to search in for imported modules").multiple().delimiter(File.pathSeparator)
|
||||||
val compilationTarget by cli.option(ArgType.String, fullName = "target", description = "target output of the compiler (one of '${C64Target.NAME}', '${C128Target.NAME}', '${Cx16Target.NAME}', '${AtariTarget.NAME}', '${VMTarget.NAME}')").default(C64Target.NAME)
|
val compilationTarget by cli.option(ArgType.String, fullName = "target", description = "target output of the compiler (one of '${C64Target.NAME}', '${C128Target.NAME}', '${Cx16Target.NAME}', '${AtariTarget.NAME}', '${VMTarget.NAME}')").default(C64Target.NAME)
|
||||||
val startVm by cli.option(ArgType.Boolean, fullName = "vm", description = "load and run a p8-virt or p8-ir listing in the VM instead")
|
val startVm by cli.option(ArgType.Boolean, fullName = "vm", description = "load and run a .p8ir IR source file in the VM")
|
||||||
val watchMode by cli.option(ArgType.Boolean, fullName = "watch", description = "continuous compilation mode (watch for file changes)")
|
val watchMode by cli.option(ArgType.Boolean, fullName = "watch", description = "continuous compilation mode (watch for file changes)")
|
||||||
val moduleFiles by cli.argument(ArgType.String, fullName = "modules", description = "main module file(s) to compile").multiple(999)
|
val moduleFiles by cli.argument(ArgType.String, fullName = "modules", description = "main module file(s) to compile").multiple(999)
|
||||||
|
|
||||||
@@ -247,26 +250,6 @@ private fun processSymbolDefs(symbolDefs: List<String>): Map<String, String>? {
|
|||||||
|
|
||||||
fun runVm(irFilename: String): Boolean {
|
fun runVm(irFilename: String): Boolean {
|
||||||
val irFile = Path(irFilename)
|
val irFile = Path(irFilename)
|
||||||
if(irFilename.endsWith(".p8ir")) {
|
|
||||||
val withoutSuffix = irFilename.substring(0, irFilename.length-5)
|
|
||||||
val compiled = VmCodeGen.compileIR(irFile)
|
|
||||||
if (!compiled.assemble(CompilationOptions( // these are just dummy options, the actual options are inside the .p8ir file itself:
|
|
||||||
OutputType.PRG,
|
|
||||||
CbmPrgLauncherType.NONE,
|
|
||||||
ZeropageType.DONTUSE,
|
|
||||||
emptyList(),
|
|
||||||
floats = true,
|
|
||||||
noSysInit = true,
|
|
||||||
compTarget = VMTarget(),
|
|
||||||
loadAddress = VMTarget().machine.PROGRAM_LOAD_ADDRESS
|
|
||||||
))
|
|
||||||
) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
val vmdef = VirtualMachineDefinition()
|
|
||||||
vmdef.launchEmulator(0, Paths.get(withoutSuffix))
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
val vmdef = VirtualMachineDefinition()
|
val vmdef = VirtualMachineDefinition()
|
||||||
vmdef.launchEmulator(0, irFile)
|
vmdef.launchEmulator(0, irFile)
|
||||||
return true
|
return true
|
||||||
|
@@ -10,8 +10,27 @@ class TestLaunchEmu: FunSpec({
|
|||||||
|
|
||||||
test("test launch virtualmachine via target") {
|
test("test launch virtualmachine via target") {
|
||||||
val target = VMTarget()
|
val target = VMTarget()
|
||||||
val tmpfile = kotlin.io.path.createTempFile(suffix=".p8virt")
|
val tmpfile = kotlin.io.path.createTempFile(suffix=".p8ir")
|
||||||
tmpfile.writeText(";comment\n------PROGRAM------\n;comment\n")
|
tmpfile.writeText("""<PROGRAM NAME=test>
|
||||||
|
<OPTIONS>
|
||||||
|
</OPTIONS>
|
||||||
|
|
||||||
|
<VARIABLES>
|
||||||
|
</VARIABLES>
|
||||||
|
|
||||||
|
<MEMORYMAPPEDVARIABLES>
|
||||||
|
</MEMORYMAPPEDVARIABLES>
|
||||||
|
|
||||||
|
<MEMORYSLABS>
|
||||||
|
</MEMORYSLABS>
|
||||||
|
|
||||||
|
<INITGLOBALS>
|
||||||
|
</INITGLOBALS>
|
||||||
|
|
||||||
|
<BLOCK NAME=main ADDRESS=null ALIGN=NONE POS=[unittest: line 42 col 1-9]>
|
||||||
|
</BLOCK>
|
||||||
|
</PROGRAM>
|
||||||
|
""")
|
||||||
target.machine.launchEmulator(0, tmpfile)
|
target.machine.launchEmulator(0, tmpfile)
|
||||||
tmpfile.deleteExisting()
|
tmpfile.deleteExisting()
|
||||||
}
|
}
|
||||||
|
@@ -26,10 +26,10 @@ main {
|
|||||||
}"""
|
}"""
|
||||||
val target = VMTarget()
|
val target = VMTarget()
|
||||||
val result = compileText(target, true, src, writeAssembly = true, keepIR=true)!!
|
val result = compileText(target, true, src, writeAssembly = true, keepIR=true)!!
|
||||||
val virtfile = result.compilationOptions.outputDir.resolve(result.program.name + ".p8virt")
|
val virtfile = result.compilationOptions.outputDir.resolve(result.program.name + ".p8ir")
|
||||||
VmRunner().runProgram(virtfile.readText())
|
VmRunner().runProgram(virtfile.readText())
|
||||||
val result2 = compileText(target, true, src, writeAssembly = true, keepIR=false)!!
|
val result2 = compileText(target, true, src, writeAssembly = true, keepIR=false)!!
|
||||||
val virtfile2 = result2.compilationOptions.outputDir.resolve(result2.program.name + ".p8virt")
|
val virtfile2 = result2.compilationOptions.outputDir.resolve(result2.program.name + ".p8ir")
|
||||||
VmRunner().runProgram(virtfile2.readText())
|
VmRunner().runProgram(virtfile2.readText())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -51,10 +51,10 @@ main {
|
|||||||
compileText(othertarget, true, src, writeAssembly = true, keepIR=true) shouldNotBe null
|
compileText(othertarget, true, src, writeAssembly = true, keepIR=true) shouldNotBe null
|
||||||
val target = VMTarget()
|
val target = VMTarget()
|
||||||
val result = compileText(target, true, src, writeAssembly = true, keepIR=true)!!
|
val result = compileText(target, true, src, writeAssembly = true, keepIR=true)!!
|
||||||
val virtfile = result.compilationOptions.outputDir.resolve(result.program.name + ".p8virt")
|
val virtfile = result.compilationOptions.outputDir.resolve(result.program.name + ".p8ir")
|
||||||
VmRunner().runProgram(virtfile.readText())
|
VmRunner().runProgram(virtfile.readText())
|
||||||
val result2 = compileText(target, true, src, writeAssembly = true, keepIR=false)!!
|
val result2 = compileText(target, true, src, writeAssembly = true, keepIR=false)!!
|
||||||
val virtfile2 = result2.compilationOptions.outputDir.resolve(result2.program.name + ".p8virt")
|
val virtfile2 = result2.compilationOptions.outputDir.resolve(result2.program.name + ".p8ir")
|
||||||
VmRunner().runProgram(virtfile2.readText())
|
VmRunner().runProgram(virtfile2.readText())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -72,10 +72,10 @@ main {
|
|||||||
}"""
|
}"""
|
||||||
val target = VMTarget()
|
val target = VMTarget()
|
||||||
val result = compileText(target, true, src, writeAssembly = true, keepIR=true)!!
|
val result = compileText(target, true, src, writeAssembly = true, keepIR=true)!!
|
||||||
val virtfile = result.compilationOptions.outputDir.resolve(result.program.name + ".p8virt")
|
val virtfile = result.compilationOptions.outputDir.resolve(result.program.name + ".p8ir")
|
||||||
VmRunner().runProgram(virtfile.readText())
|
VmRunner().runProgram(virtfile.readText())
|
||||||
val result2 = compileText(target, true, src, writeAssembly = true, keepIR=false)!!
|
val result2 = compileText(target, true, src, writeAssembly = true, keepIR=false)!!
|
||||||
val virtfile2 = result2.compilationOptions.outputDir.resolve(result2.program.name + ".p8virt")
|
val virtfile2 = result2.compilationOptions.outputDir.resolve(result2.program.name + ".p8ir")
|
||||||
VmRunner().runProgram(virtfile2.readText())
|
VmRunner().runProgram(virtfile2.readText())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -115,10 +115,10 @@ mylabel_inside:
|
|||||||
|
|
||||||
val target = VMTarget()
|
val target = VMTarget()
|
||||||
val result = compileText(target, true, src, writeAssembly = true, keepIR=true)!!
|
val result = compileText(target, true, src, writeAssembly = true, keepIR=true)!!
|
||||||
val virtfile = result.compilationOptions.outputDir.resolve(result.program.name + ".p8virt")
|
val virtfile = result.compilationOptions.outputDir.resolve(result.program.name + ".p8ir")
|
||||||
VmRunner().runProgram(virtfile.readText())
|
VmRunner().runProgram(virtfile.readText())
|
||||||
val result2 = compileText(target, true, src, writeAssembly = true, keepIR=false)!!
|
val result2 = compileText(target, true, src, writeAssembly = true, keepIR=false)!!
|
||||||
val virtfile2 = result2.compilationOptions.outputDir.resolve(result2.program.name + ".p8virt")
|
val virtfile2 = result2.compilationOptions.outputDir.resolve(result2.program.name + ".p8ir")
|
||||||
VmRunner().runProgram(virtfile2.readText())
|
VmRunner().runProgram(virtfile2.readText())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -143,14 +143,14 @@ skipLABEL:
|
|||||||
compileText(othertarget, true, src, writeAssembly = true, keepIR=true) shouldNotBe null
|
compileText(othertarget, true, src, writeAssembly = true, keepIR=true) shouldNotBe null
|
||||||
val target = VMTarget()
|
val target = VMTarget()
|
||||||
val result = compileText(target, true, src, writeAssembly = true, keepIR=true)!!
|
val result = compileText(target, true, src, writeAssembly = true, keepIR=true)!!
|
||||||
val virtfile = result.compilationOptions.outputDir.resolve(result.program.name + ".p8virt")
|
val virtfile = result.compilationOptions.outputDir.resolve(result.program.name + ".p8ir")
|
||||||
VmRunner().runAndTestProgram(virtfile.readText()) { vm ->
|
VmRunner().runAndTestProgram(virtfile.readText()) { vm ->
|
||||||
vm.memory.getUB(0) shouldBe 42u
|
vm.memory.getUB(0) shouldBe 42u
|
||||||
vm.memory.getUB(3) shouldBe 66u
|
vm.memory.getUB(3) shouldBe 66u
|
||||||
}
|
}
|
||||||
|
|
||||||
val result2 = compileText(target, true, src, writeAssembly = true, keepIR=false)!!
|
val result2 = compileText(target, true, src, writeAssembly = true, keepIR=false)!!
|
||||||
val virtfile2 = result2.compilationOptions.outputDir.resolve(result2.program.name + ".p8virt")
|
val virtfile2 = result2.compilationOptions.outputDir.resolve(result2.program.name + ".p8ir")
|
||||||
VmRunner().runAndTestProgram(virtfile2.readText()) { vm ->
|
VmRunner().runAndTestProgram(virtfile2.readText()) { vm ->
|
||||||
vm.memory.getUB(0) shouldBe 42u
|
vm.memory.getUB(0) shouldBe 42u
|
||||||
vm.memory.getUB(3) shouldBe 66u
|
vm.memory.getUB(3) shouldBe 66u
|
||||||
|
@@ -3,7 +3,9 @@ TODO
|
|||||||
|
|
||||||
For next release
|
For next release
|
||||||
^^^^^^^^^^^^^^^^
|
^^^^^^^^^^^^^^^^
|
||||||
- vm: get rid of p8virt format and Assembler, run p8ir directly
|
- vm: merge IRCodeInstruction into Instruction directly
|
||||||
|
- vm: actually translate IRProgram to vm program list again.
|
||||||
|
- docs: modify compiler arch picture once this is done.
|
||||||
|
|
||||||
...
|
...
|
||||||
|
|
||||||
|
@@ -126,7 +126,7 @@ class IRAsmSubroutine(val name: String,
|
|||||||
|
|
||||||
sealed class IRCodeLine
|
sealed class IRCodeLine
|
||||||
|
|
||||||
class IRCodeInstruction(
|
class IRCodeInstruction( // TODO join this into Instruction directly
|
||||||
opcode: Opcode,
|
opcode: Opcode,
|
||||||
type: VmDataType?=null,
|
type: VmDataType?=null,
|
||||||
reg1: Int?=null, // 0-$ffff
|
reg1: Int?=null, // 0-$ffff
|
||||||
@@ -137,7 +137,7 @@ class IRCodeInstruction(
|
|||||||
fpValue: Float?=null,
|
fpValue: Float?=null,
|
||||||
labelSymbol: String?=null // alternative to value
|
labelSymbol: String?=null // alternative to value
|
||||||
): IRCodeLine() {
|
): IRCodeLine() {
|
||||||
val ins = Instruction(opcode, type, reg1, reg2, fpReg1, fpReg2, value, fpValue, if(labelSymbol==null) null else listOf(labelSymbol))
|
val ins = Instruction(opcode, type, reg1, reg2, fpReg1, fpReg2, value, fpValue, labelSymbol)
|
||||||
|
|
||||||
init {
|
init {
|
||||||
if(reg1!=null && (reg1<0 || reg1>65536))
|
if(reg1!=null && (reg1<0 || reg1>65536))
|
||||||
|
@@ -14,9 +14,10 @@ Value stack, max 128 entries of 1 byte each.
|
|||||||
Status flags: Carry, Zero, Negative. NOTE: status flags are only affected by the CMP instruction or explicit CLC/SEC!!!
|
Status flags: Carry, Zero, Negative. NOTE: status flags are only affected by the CMP instruction or explicit CLC/SEC!!!
|
||||||
logical or arithmetic operations DO NOT AFFECT THE STATUS FLAGS UNLESS EXPLICITLY NOTED!
|
logical or arithmetic operations DO NOT AFFECT THE STATUS FLAGS UNLESS EXPLICITLY NOTED!
|
||||||
|
|
||||||
Most instructions have an associated data type 'b','w','f'. (omitting it means 'b'/byte).
|
Instruction set is mostly a load/store architecture, there are few instructions operating on memory directly.
|
||||||
|
Most instructions have an associated data type 'b','w','f'. (omitting it defaults to 'b' - byte).
|
||||||
Currently NO support for 24 or 32 bits integers.
|
Currently NO support for 24 or 32 bits integers.
|
||||||
Floating point operations are just 'f' typed regular instructions, and additionally there are a few fp conversion instructions
|
Floating point operations are just 'f' typed regular instructions, however there are a few unique fp conversion instructions.
|
||||||
|
|
||||||
|
|
||||||
LOAD/STORE
|
LOAD/STORE
|
||||||
@@ -406,7 +407,7 @@ data class Instruction(
|
|||||||
val fpReg2: Int?=null, // 0-$ffff
|
val fpReg2: Int?=null, // 0-$ffff
|
||||||
val value: Int?=null, // 0-$ffff
|
val value: Int?=null, // 0-$ffff
|
||||||
val fpValue: Float?=null,
|
val fpValue: Float?=null,
|
||||||
val labelSymbol: List<String>?=null, // symbolic label name as alternative to value (so only for Branch/jump/call Instructions!)
|
val labelSymbol: String?=null, // symbolic label name as alternative to value (so only for Branch/jump/call Instructions!)
|
||||||
val binaryData: ByteArray?=null
|
val binaryData: ByteArray?=null
|
||||||
) {
|
) {
|
||||||
// reg1 and fpreg1 can be IN/OUT/INOUT (all others are readonly INPUT)
|
// reg1 and fpreg1 can be IN/OUT/INOUT (all others are readonly INPUT)
|
||||||
@@ -494,10 +495,10 @@ data class Instruction(
|
|||||||
result.add(",")
|
result.add(",")
|
||||||
}
|
}
|
||||||
labelSymbol?.let {
|
labelSymbol?.let {
|
||||||
if(labelSymbol[0].startsWith('&'))
|
if(it.startsWith('&'))
|
||||||
result.add(it.joinToString(".")) // address-of something
|
result.add(it) // address-of something
|
||||||
else
|
else
|
||||||
result.add("_" + it.joinToString("."))
|
result.add("_$it")
|
||||||
}
|
}
|
||||||
if(result.last() == ",")
|
if(result.last() == ",")
|
||||||
result.removeLast()
|
result.removeLast()
|
||||||
|
@@ -34,7 +34,7 @@ class TestInstructions: FunSpec({
|
|||||||
}
|
}
|
||||||
|
|
||||||
test("with label") {
|
test("with label") {
|
||||||
val ins = Instruction(Opcode.BZ, VmDataType.WORD, reg1=11, labelSymbol = listOf("a","b","c"))
|
val ins = Instruction(Opcode.BZ, VmDataType.WORD, reg1=11, labelSymbol = "a.b.c")
|
||||||
ins.opcode shouldBe Opcode.BZ
|
ins.opcode shouldBe Opcode.BZ
|
||||||
ins.type shouldBe VmDataType.WORD
|
ins.type shouldBe VmDataType.WORD
|
||||||
ins.reg1 shouldBe 11
|
ins.reg1 shouldBe 11
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
#!/usr/bin/env sh
|
#!/usr/bin/env sh
|
||||||
|
|
||||||
rm -f *.bin *.xex *.jar *.asm *.prg *.vm.txt *.vice-mon-list *.list *.p8virt a.out imgui.ini
|
rm -f *.bin *.xex *.jar *.asm *.prg *.vm.txt *.vice-mon-list *.list *.p8ir a.out imgui.ini
|
||||||
rm -rf build out
|
rm -rf build out
|
||||||
rm -rf compiler/build codeGenCpu6502/build codeGenExperimental/build codeOptimizers/build compilerAst/build dbusCompilerService/build httpCompilerService/build parser/build parser/src/prog8/parser
|
rm -rf compiler/build codeGenCpu6502/build codeGenExperimental/build codeOptimizers/build compilerAst/build dbusCompilerService/build httpCompilerService/build parser/build parser/src/prog8/parser
|
||||||
|
|
||||||
|
@@ -1,349 +0,0 @@
|
|||||||
package prog8.vm
|
|
||||||
|
|
||||||
import prog8.code.core.unescape
|
|
||||||
import prog8.intermediate.*
|
|
||||||
|
|
||||||
|
|
||||||
class Assembler {
|
|
||||||
private val symbolAddresses = mutableMapOf<String, Int>()
|
|
||||||
private val placeholders = mutableMapOf<Int, String>()
|
|
||||||
var cx16virtualregBaseAdress = 0xff02
|
|
||||||
|
|
||||||
init {
|
|
||||||
require(instructionFormats.size== Opcode.values().size) {
|
|
||||||
"missing " + (Opcode.values().toSet() - instructionFormats.keys)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun initializeMemory(memsrc: String, memory: Memory) {
|
|
||||||
symbolAddresses.clear()
|
|
||||||
val arrayValuePlaceholders = mutableListOf<Pair<Int, String>>()
|
|
||||||
|
|
||||||
val instrPattern = Regex("""var (.+) @([0-9]+) ([a-z]+)(\[[0-9]+\])? (.+)""", RegexOption.IGNORE_CASE)
|
|
||||||
for(line in memsrc.lines()) {
|
|
||||||
if(line.isBlank() || line.startsWith(';'))
|
|
||||||
continue
|
|
||||||
val match = instrPattern.matchEntire(line.trim())
|
|
||||||
if(match==null)
|
|
||||||
throw IllegalArgumentException("invalid line $line")
|
|
||||||
else {
|
|
||||||
val (name, addrStr, datatype, arrayspec, values) = match.destructured
|
|
||||||
if(name=="cx16.r0") {
|
|
||||||
cx16virtualregBaseAdress = addrStr.toInt()
|
|
||||||
}
|
|
||||||
val numArrayElts = if(arrayspec.isBlank()) 1 else arrayspec.substring(1, arrayspec.length-1).toInt()
|
|
||||||
var address = parseValue(Opcode.LOADCPU, addrStr, 0).toInt()
|
|
||||||
symbolAddresses[name] = address
|
|
||||||
when(datatype) {
|
|
||||||
"str" -> {
|
|
||||||
val string = values.trim('"').unescape()
|
|
||||||
memory.setString(address, string, false)
|
|
||||||
}
|
|
||||||
"strz" -> {
|
|
||||||
val string = values.trim('"').unescape()
|
|
||||||
memory.setString(address, string, true)
|
|
||||||
}
|
|
||||||
"ubyte", "byte" -> {
|
|
||||||
val array = values.split(',').map { parseValue(Opcode.LOADCPU, it.trim(), 0).toInt() }
|
|
||||||
require(array.size==numArrayElts || array.size==1)
|
|
||||||
if(numArrayElts > array.size) {
|
|
||||||
val value = array.single().toUByte()
|
|
||||||
repeat(numArrayElts) {
|
|
||||||
memory.setUB(address, value)
|
|
||||||
address++
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
for (value in array) {
|
|
||||||
memory.setUB(address, value.toUByte())
|
|
||||||
address++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
"uword", "word" -> {
|
|
||||||
if(arrayspec.isBlank()) {
|
|
||||||
// single value
|
|
||||||
val value = parseValue(Opcode.LOADCPU, values.trim(), 0).toInt()
|
|
||||||
memory.setUW(address, value.toUShort())
|
|
||||||
address += 2
|
|
||||||
} else {
|
|
||||||
// array initializer
|
|
||||||
val array = values.split(',').withIndex().map {(index, value) ->
|
|
||||||
val tv = value.trim()
|
|
||||||
if(tv.startsWith('&')) {
|
|
||||||
arrayValuePlaceholders += Pair(address+index*2, tv.drop(1))
|
|
||||||
9999 // will be replaced with correct value at the end.
|
|
||||||
} else
|
|
||||||
parseValue(Opcode.LOADCPU, tv, 0).toInt()
|
|
||||||
}
|
|
||||||
require(array.size==numArrayElts || array.size==1)
|
|
||||||
if(numArrayElts>array.size) {
|
|
||||||
val value = array.single().toUShort()
|
|
||||||
repeat(numArrayElts) {
|
|
||||||
memory.setUW(address, value)
|
|
||||||
address += 2
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
for (value in array) {
|
|
||||||
memory.setUW(address, value.toUShort())
|
|
||||||
address += 2
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
"float" -> {
|
|
||||||
val array = values.split(',').map { it.toFloat() }
|
|
||||||
require(array.size==numArrayElts || array.size==1)
|
|
||||||
if(numArrayElts>array.size) {
|
|
||||||
val value = array.single()
|
|
||||||
repeat(numArrayElts) {
|
|
||||||
memory.setFloat(address, value)
|
|
||||||
address += 4 // 32-bits floats
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
for (value in array) {
|
|
||||||
memory.setFloat(address, value)
|
|
||||||
address += 4 // 32-bits floats
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else -> throw IllegalArgumentException("invalid datatype $datatype")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// correct the addres-of values in array initializers
|
|
||||||
arrayValuePlaceholders.forEach { (address, symbol) ->
|
|
||||||
val addr = this.symbolAddresses.getValue(symbol)
|
|
||||||
memory.setUW(address, addr.toUShort())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun assembleProgram(source: CharSequence): List<Instruction> {
|
|
||||||
placeholders.clear()
|
|
||||||
val program = mutableListOf<Instruction>()
|
|
||||||
val instructionPattern = Regex("""([a-z]+)(\.b|\.w|\.f)?(.*)""", RegexOption.IGNORE_CASE)
|
|
||||||
val labelPattern = Regex("""_([a-zA-Z\d\._]+):""")
|
|
||||||
val binaryPattern = Regex("""!binary (.+)""")
|
|
||||||
for (line in source.lines()) {
|
|
||||||
if(line.isBlank() || line.startsWith(';'))
|
|
||||||
continue
|
|
||||||
val match = instructionPattern.matchEntire(line.trim())
|
|
||||||
if(match==null) {
|
|
||||||
val binarymatch = binaryPattern.matchEntire(line.trim())
|
|
||||||
if(binarymatch!=null) {
|
|
||||||
val hex = binarymatch.groups[1]!!.value
|
|
||||||
val binary = hex.windowed(size=2, step=2).map {
|
|
||||||
it.toByte(16)
|
|
||||||
}.toByteArray()
|
|
||||||
program.add(Instruction(Opcode.BINARYDATA, binaryData = binary))
|
|
||||||
} else {
|
|
||||||
val labelmatch = labelPattern.matchEntire(line.trim())
|
|
||||||
if (labelmatch == null)
|
|
||||||
throw IllegalArgumentException("invalid line $line at line ${program.size + 1}")
|
|
||||||
else {
|
|
||||||
val label = labelmatch.groupValues[1]
|
|
||||||
if (label in symbolAddresses)
|
|
||||||
throw IllegalArgumentException("label redefined $label")
|
|
||||||
symbolAddresses[label] = program.size
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
val (_, instr, typestr, rest) = match.groupValues
|
|
||||||
if(instr=="incbin") {
|
|
||||||
println("warning: ignoring incbin command: $rest")
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
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('_')) {
|
|
||||||
placeholders[pc] = rest.split(",")[restIndex].trim().drop(1)
|
|
||||||
0f
|
|
||||||
} else if(operand[0].isLetter()) {
|
|
||||||
placeholders[pc] = rest.split(",")[restIndex].trim()
|
|
||||||
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.add(Instruction(opcode, type, reg1, labelSymbol = listOf(reg)))
|
|
||||||
} else {
|
|
||||||
program.add(Instruction(opcode, type, reg1, reg2, fpReg1, fpReg2, intValue, floatValue))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pass2replaceLabels(program)
|
|
||||||
return program
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun pass2replaceLabels(program: MutableList<Instruction>) {
|
|
||||||
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 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")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,43 +0,0 @@
|
|||||||
package prog8.vm
|
|
||||||
|
|
||||||
fun main(args: Array<String>) {
|
|
||||||
val memsrc = """
|
|
||||||
$4000 strz "Hello from program! "derp" bye.\n"
|
|
||||||
$2000 ubyte 65,66,67,68,0
|
|
||||||
$2100 uword $1111,$2222,$3333,$4444
|
|
||||||
"""
|
|
||||||
val src = """
|
|
||||||
; enable lores gfx screen
|
|
||||||
load r0, 0
|
|
||||||
syscall 8
|
|
||||||
load.w r10, 320
|
|
||||||
load.w r11, 240
|
|
||||||
load.b r12, 0
|
|
||||||
|
|
||||||
_forever:
|
|
||||||
load.w r1, 0
|
|
||||||
_yloop:
|
|
||||||
load.w r0, 0
|
|
||||||
_xloop:
|
|
||||||
mul.b r2,r0,r1
|
|
||||||
add.b r2,r2,r12
|
|
||||||
syscall 10
|
|
||||||
addi.w r0,r0,1
|
|
||||||
blt.w r0, r10, _xloop
|
|
||||||
addi.w r1,r1,1
|
|
||||||
blt.w r1, r11,_yloop
|
|
||||||
addi.b r12,r12,1
|
|
||||||
jump _forever
|
|
||||||
|
|
||||||
load.w r0, 2000
|
|
||||||
syscall 7
|
|
||||||
load.w r0,0
|
|
||||||
return"""
|
|
||||||
val memory = Memory()
|
|
||||||
val assembler = Assembler()
|
|
||||||
assembler.initializeMemory(memsrc, memory)
|
|
||||||
val program = assembler.assembleProgram(src)
|
|
||||||
|
|
||||||
val vm = VirtualMachine(memory, program, assembler.cx16virtualregBaseAdress)
|
|
||||||
vm.run()
|
|
||||||
}
|
|
@@ -1,9 +1,7 @@
|
|||||||
package prog8.vm
|
package prog8.vm
|
||||||
|
|
||||||
import prog8.code.target.virtual.IVirtualMachineRunner
|
import prog8.code.target.virtual.IVirtualMachineRunner
|
||||||
import prog8.intermediate.Instruction
|
import prog8.intermediate.*
|
||||||
import prog8.intermediate.Opcode
|
|
||||||
import prog8.intermediate.VmDataType
|
|
||||||
import java.awt.Color
|
import java.awt.Color
|
||||||
import java.awt.Toolkit
|
import java.awt.Toolkit
|
||||||
import java.util.*
|
import java.util.*
|
||||||
@@ -31,9 +29,10 @@ class BreakpointException(val pc: Int): Exception()
|
|||||||
|
|
||||||
|
|
||||||
@Suppress("FunctionName")
|
@Suppress("FunctionName")
|
||||||
class VirtualMachine(val memory: Memory, program: List<Instruction>, val cx16virtualregsBaseAddress: Int) {
|
class VirtualMachine(irProgram: IRProgram) {
|
||||||
|
val memory = Memory()
|
||||||
|
val program: Array<Instruction> = VmProgramLoader().load(irProgram, memory) // TODO convert irProgram
|
||||||
val registers = Registers()
|
val registers = Registers()
|
||||||
val program: Array<Instruction> = program.toTypedArray()
|
|
||||||
val callStack = Stack<Int>()
|
val callStack = Stack<Int>()
|
||||||
val valueStack = Stack<UByte>() // max 128 entries
|
val valueStack = Stack<UByte>() // max 128 entries
|
||||||
var pc = 0
|
var pc = 0
|
||||||
@@ -41,6 +40,7 @@ class VirtualMachine(val memory: Memory, program: List<Instruction>, val cx16vir
|
|||||||
var statusCarry = false
|
var statusCarry = false
|
||||||
var statusZero = false
|
var statusZero = false
|
||||||
var statusNegative = false
|
var statusNegative = false
|
||||||
|
val cx16virtualregsBaseAddress = 0xff00 // TODO obtain from irProgram
|
||||||
|
|
||||||
init {
|
init {
|
||||||
if(program.size>65536)
|
if(program.size>65536)
|
||||||
@@ -305,7 +305,7 @@ class VirtualMachine(val memory: Memory, program: List<Instruction>, val cx16vir
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun InsLOADCPU(i: Instruction) {
|
private fun InsLOADCPU(i: Instruction) {
|
||||||
val reg = i.labelSymbol!!.single()
|
val reg = i.labelSymbol!!
|
||||||
val value: UInt
|
val value: UInt
|
||||||
if(reg.startsWith('r')) {
|
if(reg.startsWith('r')) {
|
||||||
val regnum = reg.substring(1).toInt()
|
val regnum = reg.substring(1).toInt()
|
||||||
@@ -340,12 +340,12 @@ class VirtualMachine(val memory: Memory, program: List<Instruction>, val cx16vir
|
|||||||
VmDataType.WORD -> registers.getUW(i.reg1!!).toUInt()
|
VmDataType.WORD -> registers.getUW(i.reg1!!).toUInt()
|
||||||
VmDataType.FLOAT -> throw IllegalArgumentException("there are no float cpu registers")
|
VmDataType.FLOAT -> throw IllegalArgumentException("there are no float cpu registers")
|
||||||
}
|
}
|
||||||
StoreCPU(value, i.type!!, i.labelSymbol!!.single())
|
StoreCPU(value, i.type!!, i.labelSymbol!!)
|
||||||
pc++
|
pc++
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun InsSTOREZCPU(i: Instruction) {
|
private fun InsSTOREZCPU(i: Instruction) {
|
||||||
StoreCPU(0u, i.type!!, i.labelSymbol!!.single())
|
StoreCPU(0u, i.type!!, i.labelSymbol!!)
|
||||||
pc++
|
pc++
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2105,17 +2105,13 @@ class VirtualMachine(val memory: Memory, program: List<Instruction>, val cx16vir
|
|||||||
|
|
||||||
// probably called via reflection
|
// probably called via reflection
|
||||||
class VmRunner: IVirtualMachineRunner {
|
class VmRunner: IVirtualMachineRunner {
|
||||||
override fun runProgram(source: String) {
|
override fun runProgram(irSource: CharSequence) {
|
||||||
runAndTestProgram(source) { /* no tests */ }
|
runAndTestProgram(irSource) { /* no tests */ }
|
||||||
}
|
}
|
||||||
|
|
||||||
fun runAndTestProgram(source: String, test: (VirtualMachine) -> Unit) {
|
fun runAndTestProgram(irSource: CharSequence, test: (VirtualMachine) -> Unit) {
|
||||||
val (memsrc, programsrc) = source.split("------PROGRAM------".toRegex(), 2)
|
val irProgram = IRFileReader().read(irSource)
|
||||||
val memory = Memory()
|
val vm = VirtualMachine(irProgram)
|
||||||
val assembler = Assembler()
|
|
||||||
assembler.initializeMemory(memsrc, memory)
|
|
||||||
val program = assembler.assembleProgram(programsrc)
|
|
||||||
val vm = VirtualMachine(memory, program, assembler.cx16virtualregBaseAdress)
|
|
||||||
vm.run()
|
vm.run()
|
||||||
test(vm)
|
test(vm)
|
||||||
}
|
}
|
||||||
|
56
virtualmachine/src/prog8/vm/VmProgramLoader.kt
Normal file
56
virtualmachine/src/prog8/vm/VmProgramLoader.kt
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
package prog8.vm
|
||||||
|
|
||||||
|
import prog8.intermediate.*
|
||||||
|
import java.lang.IllegalArgumentException
|
||||||
|
|
||||||
|
class VmProgramLoader {
|
||||||
|
|
||||||
|
fun load(irProgram: IRProgram, memory: Memory): Array<Instruction> {
|
||||||
|
|
||||||
|
// at long last, allocate the variables in memory.
|
||||||
|
val allocations = VmVariableAllocator(irProgram.st, irProgram.encoding, irProgram.options.compTarget)
|
||||||
|
val program = mutableListOf<Instruction>()
|
||||||
|
|
||||||
|
// TODO stuff the allocated variables into memory
|
||||||
|
|
||||||
|
if(!irProgram.options.dontReinitGlobals) {
|
||||||
|
irProgram.globalInits.forEach {
|
||||||
|
// TODO put global init line into program
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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(Instruction(Opcode.JUMP, labelSymbol = "main.start"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
irProgram.blocks.forEach { block ->
|
||||||
|
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.subroutines.forEach {
|
||||||
|
// TODO subroutine label ?
|
||||||
|
it.chunks.forEach { chunk ->
|
||||||
|
if(chunk is IRInlineAsmChunk) {
|
||||||
|
// TODO put it.assembly into program
|
||||||
|
} else {
|
||||||
|
chunk.lines.forEach {
|
||||||
|
// TODO put line into program
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
block.asmSubroutines.forEach {
|
||||||
|
// TODO add asmsub to program
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return emptyArray()
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@@ -1,4 +1,4 @@
|
|||||||
package prog8.codegen.virtual
|
package prog8.vm
|
||||||
|
|
||||||
import prog8.code.core.*
|
import prog8.code.core.*
|
||||||
import prog8.intermediate.IRSymbolTable
|
import prog8.intermediate.IRSymbolTable
|
@@ -1,17 +1,31 @@
|
|||||||
import io.kotest.core.spec.style.FunSpec
|
import io.kotest.core.spec.style.FunSpec
|
||||||
import io.kotest.matchers.collections.shouldBeEmpty
|
import io.kotest.matchers.collections.shouldBeEmpty
|
||||||
import io.kotest.matchers.shouldBe
|
import io.kotest.matchers.shouldBe
|
||||||
import prog8.intermediate.Instruction
|
import prog8.code.core.*
|
||||||
import prog8.intermediate.Opcode
|
import prog8.code.target.VMTarget
|
||||||
import prog8.intermediate.VmDataType
|
import prog8.intermediate.*
|
||||||
import prog8.vm.Memory
|
|
||||||
import prog8.vm.VirtualMachine
|
import prog8.vm.VirtualMachine
|
||||||
import prog8.vm.VmRunner
|
import prog8.vm.VmRunner
|
||||||
|
|
||||||
class TestVm: FunSpec( {
|
class TestVm: FunSpec( {
|
||||||
|
|
||||||
|
fun getTestOptions(): CompilationOptions {
|
||||||
|
val target = VMTarget()
|
||||||
|
return CompilationOptions(
|
||||||
|
OutputType.RAW,
|
||||||
|
CbmPrgLauncherType.NONE,
|
||||||
|
ZeropageType.DONTUSE,
|
||||||
|
zpReserved = emptyList(),
|
||||||
|
floats = true,
|
||||||
|
noSysInit = false,
|
||||||
|
compTarget = target,
|
||||||
|
loadAddress = target.machine.PROGRAM_LOAD_ADDRESS
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
test("vm execution: empty program") {
|
test("vm execution: empty program") {
|
||||||
val memory = Memory()
|
val program = IRProgram("test", IRSymbolTable(null), getTestOptions(), VMTarget())
|
||||||
val vm = VirtualMachine(memory, emptyList(), 0xff00)
|
val vm = VirtualMachine(program)
|
||||||
vm.callStack.shouldBeEmpty()
|
vm.callStack.shouldBeEmpty()
|
||||||
vm.valueStack.shouldBeEmpty()
|
vm.valueStack.shouldBeEmpty()
|
||||||
vm.pc shouldBe 0
|
vm.pc shouldBe 0
|
||||||
@@ -24,21 +38,25 @@ class TestVm: FunSpec( {
|
|||||||
}
|
}
|
||||||
|
|
||||||
test("vm execution: modify memory") {
|
test("vm execution: modify memory") {
|
||||||
val memory = Memory()
|
val program = IRProgram("test", IRSymbolTable(null), getTestOptions(), VMTarget())
|
||||||
val program = listOf(
|
val block = IRBlock("main", null, IRBlock.BlockAlignment.NONE, Position.DUMMY)
|
||||||
Instruction(Opcode.LOAD, VmDataType.WORD, reg1=1, value=12345),
|
val startSub = IRSubroutine("main.start2222", emptyList(), null, Position.DUMMY) // TODO proper name main.start
|
||||||
Instruction(Opcode.STOREM, VmDataType.WORD, reg1=1, value=1000),
|
val code = IRCodeChunk(Position.DUMMY)
|
||||||
Instruction(Opcode.RETURN)
|
code += IRCodeInstruction(Opcode.LOAD, VmDataType.WORD, reg1=1, value=12345)
|
||||||
)
|
code += IRCodeInstruction(Opcode.STOREM, VmDataType.WORD, reg1=1, value=1000)
|
||||||
val vm = VirtualMachine(memory, program, 0xff00)
|
code += IRCodeInstruction(Opcode.RETURN)
|
||||||
|
startSub += code
|
||||||
|
block += startSub
|
||||||
|
program.addBlock(block)
|
||||||
|
val vm = VirtualMachine(program)
|
||||||
|
|
||||||
memory.getUW(1000) shouldBe 0u
|
vm.memory.getUW(1000) shouldBe 0u
|
||||||
vm.callStack.shouldBeEmpty()
|
vm.callStack.shouldBeEmpty()
|
||||||
vm.valueStack.shouldBeEmpty()
|
vm.valueStack.shouldBeEmpty()
|
||||||
vm.pc shouldBe 0
|
vm.pc shouldBe 0
|
||||||
vm.stepCount shouldBe 0
|
vm.stepCount shouldBe 0
|
||||||
vm.run()
|
vm.run()
|
||||||
memory.getUW(1000) shouldBe 12345u
|
vm.memory.getUW(1000) shouldBe 12345u
|
||||||
vm.callStack.shouldBeEmpty()
|
vm.callStack.shouldBeEmpty()
|
||||||
vm.valueStack.shouldBeEmpty()
|
vm.valueStack.shouldBeEmpty()
|
||||||
vm.pc shouldBe 2
|
vm.pc shouldBe 2
|
||||||
@@ -47,6 +65,26 @@ class TestVm: FunSpec( {
|
|||||||
|
|
||||||
test("vmrunner") {
|
test("vmrunner") {
|
||||||
val runner = VmRunner()
|
val runner = VmRunner()
|
||||||
runner.runProgram(";comment\n------PROGRAM------\n;comment\n")
|
val irSource="""<PROGRAM NAME=test>
|
||||||
|
<OPTIONS>
|
||||||
|
</OPTIONS>
|
||||||
|
|
||||||
|
<VARIABLES>
|
||||||
|
</VARIABLES>
|
||||||
|
|
||||||
|
<MEMORYMAPPEDVARIABLES>
|
||||||
|
</MEMORYMAPPEDVARIABLES>
|
||||||
|
|
||||||
|
<MEMORYSLABS>
|
||||||
|
</MEMORYSLABS>
|
||||||
|
|
||||||
|
<INITGLOBALS>
|
||||||
|
</INITGLOBALS>
|
||||||
|
|
||||||
|
<BLOCK NAME=main ADDRESS=null ALIGN=NONE POS=[unittest: line 42 col 1-9]>
|
||||||
|
</BLOCK>
|
||||||
|
</PROGRAM>
|
||||||
|
"""
|
||||||
|
runner.runProgram(irSource)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
Reference in New Issue
Block a user