diff --git a/.idea/modules.xml b/.idea/modules.xml
index 1593991ec..5db7242c3 100644
--- a/.idea/modules.xml
+++ b/.idea/modules.xml
@@ -6,6 +6,7 @@
+
@@ -14,6 +15,7 @@
+
\ No newline at end of file
diff --git a/codeAst/codeAst.iml b/codeAst/codeAst.iml
index 7bcaf0b62..ae36eb05a 100644
--- a/codeAst/codeAst.iml
+++ b/codeAst/codeAst.iml
@@ -4,6 +4,7 @@
+
diff --git a/codeCore/build.gradle b/codeCore/build.gradle
index febb4f22b..2ecb7a4da 100644
--- a/codeCore/build.gradle
+++ b/codeCore/build.gradle
@@ -24,6 +24,7 @@ compileTestKotlin {
}
dependencies {
+ implementation project(':virtualmachine')
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8"
implementation "com.michael-bull.kotlin-result:kotlin-result-jvm:1.1.14"
}
diff --git a/codeCore/codeCore.iml b/codeCore/codeCore.iml
index 7d6a5aee3..0f6b78e46 100644
--- a/codeCore/codeCore.iml
+++ b/codeCore/codeCore.iml
@@ -4,10 +4,12 @@
+
+
\ No newline at end of file
diff --git a/codeCore/src/prog8/code/core/IMachineDefinition.kt b/codeCore/src/prog8/code/core/IMachineDefinition.kt
index ac4a4b897..c996c6500 100644
--- a/codeCore/src/prog8/code/core/IMachineDefinition.kt
+++ b/codeCore/src/prog8/code/core/IMachineDefinition.kt
@@ -10,7 +10,8 @@ interface IMachineFloat {
enum class CpuType {
CPU6502,
- CPU65c02
+ CPU65c02,
+ VIRTUAL
}
interface IMachineDefinition {
diff --git a/codeCore/src/prog8/code/target/AtariTarget.kt b/codeCore/src/prog8/code/target/AtariTarget.kt
index ef8c61d02..747cb4470 100644
--- a/codeCore/src/prog8/code/target/AtariTarget.kt
+++ b/codeCore/src/prog8/code/target/AtariTarget.kt
@@ -18,7 +18,7 @@ class AtariTarget: ICompilationTarget, IStringEncoding by Encoder, IMemSizer {
return when(dt) {
in ByteDatatypes -> 1
in WordDatatypes, in PassByReferenceDatatypes -> 2
- DataType.FLOAT -> 6
+ DataType.FLOAT -> machine.FLOAT_MEM_SIZE
else -> Int.MIN_VALUE
}
}
diff --git a/codeCore/src/prog8/code/target/VMTarget.kt b/codeCore/src/prog8/code/target/VMTarget.kt
new file mode 100644
index 000000000..06628dce2
--- /dev/null
+++ b/codeCore/src/prog8/code/target/VMTarget.kt
@@ -0,0 +1,24 @@
+package prog8.code.target
+
+import prog8.code.core.*
+import prog8.code.target.virtual.VirtualMachineDefinition
+
+class VMTarget: ICompilationTarget, IStringEncoding by Encoder, IMemSizer {
+ override val name = NAME
+ override val machine = VirtualMachineDefinition()
+ override val supportedEncodings = setOf(Encoding.ISO)
+ override val defaultEncoding = Encoding.ISO
+
+ companion object {
+ const val NAME = "virtual"
+ }
+
+ override fun memorySize(dt: DataType): Int {
+ return when(dt) {
+ in ByteDatatypes -> 1
+ in WordDatatypes, in PassByReferenceDatatypes -> 2
+ DataType.FLOAT -> machine.FLOAT_MEM_SIZE
+ else -> Int.MIN_VALUE
+ }
+ }
+}
\ No newline at end of file
diff --git a/codeCore/src/prog8/code/target/atari/AtariMachineDefinition.kt b/codeCore/src/prog8/code/target/atari/AtariMachineDefinition.kt
index 7595142c7..931ca43d4 100644
--- a/codeCore/src/prog8/code/target/atari/AtariMachineDefinition.kt
+++ b/codeCore/src/prog8/code/target/atari/AtariMachineDefinition.kt
@@ -23,7 +23,7 @@ class AtariMachineDefinition: IMachineDefinition {
override fun getFloat(num: Number) = TODO("float from number")
override fun importLibs(compilerOptions: CompilationOptions, compilationTargetName: String): List {
- return if (compilerOptions.launcher == CbmPrgLauncherType.BASIC || compilerOptions.output == OutputType.PRG)
+ return if (compilerOptions.output == OutputType.XEX)
listOf("syslib")
else
emptyList()
diff --git a/codeCore/src/prog8/code/target/virtual/VirtualMachineDefinition.kt b/codeCore/src/prog8/code/target/virtual/VirtualMachineDefinition.kt
new file mode 100644
index 000000000..97c974bc7
--- /dev/null
+++ b/codeCore/src/prog8/code/target/virtual/VirtualMachineDefinition.kt
@@ -0,0 +1,51 @@
+package prog8.code.target.virtual
+
+import prog8.code.core.CompilationOptions
+import prog8.code.core.CpuType
+import prog8.code.core.IMachineDefinition
+import prog8.code.core.Zeropage
+import prog8.vm.Assembler
+import prog8.vm.Memory
+import prog8.vm.VirtualMachine
+import java.io.File
+import java.nio.file.Path
+
+
+class VirtualMachineDefinition: IMachineDefinition {
+
+ override val cpu = CpuType.VIRTUAL
+
+ override val FLOAT_MAX_POSITIVE = Float.MAX_VALUE.toDouble()
+ override val FLOAT_MAX_NEGATIVE = -Float.MAX_VALUE.toDouble()
+ override val FLOAT_MEM_SIZE = 4
+ override val PROGRAM_LOAD_ADDRESS = 0u // not actually used
+
+ override val ESTACK_LO = 0u // not actually used
+ override val ESTACK_HI = 0u // not actually used
+
+ override lateinit var zeropage: Zeropage // not actually used
+
+ override fun getFloat(num: Number) = TODO("float from number")
+
+ override fun importLibs(compilerOptions: CompilationOptions, compilationTargetName: String): List {
+ return listOf("syslib")
+ }
+
+ override fun launchEmulator(selectedEmulator: Int, programNameWithPath: Path) {
+ println("\nStarting Virtual Machine...")
+ val source = File("$programNameWithPath.p8virt").readText()
+ val (memsrc, programsrc) = source.split("------PROGRAM------".toRegex(), 2)
+ val memory = Memory()
+ val assembler = Assembler()
+ assembler.initializeMemory(memsrc, memory)
+ val program = assembler.assembleProgram(programsrc)
+ val vm = VirtualMachine(memory, program)
+ vm.run()
+ }
+
+ override fun isIOAddress(address: UInt): Boolean = false
+
+ override fun initializeZeropage(compilerOptions: CompilationOptions) {}
+
+ override val opcodeNames = emptySet()
+}
diff --git a/codeGenCpu6502/src/prog8/codegen/cpu6502/AsmGen.kt b/codeGenCpu6502/src/prog8/codegen/cpu6502/AsmGen.kt
index 386017626..e6e7d337a 100644
--- a/codeGenCpu6502/src/prog8/codegen/cpu6502/AsmGen.kt
+++ b/codeGenCpu6502/src/prog8/codegen/cpu6502/AsmGen.kt
@@ -22,9 +22,10 @@ internal const val subroutineFloatEvalResultVar2 = "prog8_float_eval_result2"
class AsmGen(internal val program: Program,
- internal val errors: IErrorReporter,
internal val symbolTable: SymbolTable,
- internal val options: CompilationOptions): IAssemblyGenerator {
+ internal val options: CompilationOptions,
+ internal val errors: IErrorReporter
+): IAssemblyGenerator {
internal val optimizedByteMultiplications = setOf(3,5,6,7,9,10,11,12,13,14,15,20,25,40,50,80,100)
internal val optimizedWordMultiplications = setOf(3,5,6,7,9,10,12,15,20,25,40,50,80,100,320,640)
diff --git a/codeGenExperimental/src/prog8/codegen/experimental/AsmGen.kt b/codeGenExperimental/src/prog8/codegen/experimental/AsmGen.kt
index 3d7951a71..4f5b3d30e 100644
--- a/codeGenExperimental/src/prog8/codegen/experimental/AsmGen.kt
+++ b/codeGenExperimental/src/prog8/codegen/experimental/AsmGen.kt
@@ -19,9 +19,9 @@ import prog8.code.core.IErrorReporter
class AsmGen(internal val program: PtProgram,
- internal val errors: IErrorReporter,
internal val symbolTable: SymbolTable,
- internal val options: CompilationOptions
+ internal val options: CompilationOptions,
+ internal val errors: IErrorReporter
): IAssemblyGenerator {
override fun compileToAssembly(): IAssemblyProgram? {
diff --git a/codeGenVirtual/build.gradle b/codeGenVirtual/build.gradle
new file mode 100644
index 000000000..8a3071158
--- /dev/null
+++ b/codeGenVirtual/build.gradle
@@ -0,0 +1,47 @@
+
+plugins {
+ id 'java'
+ id 'application'
+ id "org.jetbrains.kotlin.jvm"
+}
+
+java {
+ toolchain {
+ languageVersion = JavaLanguageVersion.of(javaVersion)
+ }
+}
+
+compileKotlin {
+ kotlinOptions {
+ jvmTarget = javaVersion
+ }
+}
+
+compileTestKotlin {
+ kotlinOptions {
+ jvmTarget = javaVersion
+ }
+}
+
+dependencies {
+ implementation project(':virtualmachine')
+ implementation project(':codeAst')
+ implementation project(':codeCore')
+ implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8"
+ // implementation "org.jetbrains.kotlin:kotlin-reflect"
+ implementation "com.michael-bull.kotlin-result:kotlin-result-jvm:1.1.14"
+
+}
+
+sourceSets {
+ main {
+ java {
+ srcDirs = ["${project.projectDir}/src"]
+ }
+ resources {
+ srcDirs = ["${project.projectDir}/res"]
+ }
+ }
+}
+
+// note: there are no unit tests in this module!
diff --git a/codeGenVirtual/codeGenVirtual.iml b/codeGenVirtual/codeGenVirtual.iml
new file mode 100644
index 000000000..baa82a25b
--- /dev/null
+++ b/codeGenVirtual/codeGenVirtual.iml
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/codeGenVirtual/src/prog8/codegen/virtual/AssemblyProgram.kt b/codeGenVirtual/src/prog8/codegen/virtual/AssemblyProgram.kt
new file mode 100644
index 000000000..ca19d374f
--- /dev/null
+++ b/codeGenVirtual/src/prog8/codegen/virtual/AssemblyProgram.kt
@@ -0,0 +1,55 @@
+package prog8.codegen.virtual
+
+import prog8.code.core.CompilationOptions
+import prog8.code.core.IAssemblyProgram
+import kotlin.io.path.bufferedWriter
+import kotlin.io.path.div
+
+
+internal class AssemblyProgram(override val name: String) : IAssemblyProgram
+{
+ override fun assemble(options: CompilationOptions): Boolean {
+ val outfile = options.outputDir / ("$name.p8virt")
+ println("write code to ${outfile}")
+ outfile.bufferedWriter().use {
+ it.write(memsrc)
+ it.write("------PROGRAM------\n")
+ it.write(src)
+ }
+ return true
+ }
+
+ 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"""
+
+}
diff --git a/codeGenVirtual/src/prog8/codegen/virtual/CodeGen.kt b/codeGenVirtual/src/prog8/codegen/virtual/CodeGen.kt
new file mode 100644
index 000000000..7f12a42c4
--- /dev/null
+++ b/codeGenVirtual/src/prog8/codegen/virtual/CodeGen.kt
@@ -0,0 +1,20 @@
+package prog8.codegen.virtual
+
+import prog8.code.SymbolTable
+import prog8.code.ast.PtProgram
+import prog8.code.core.CompilationOptions
+import prog8.code.core.IAssemblyGenerator
+import prog8.code.core.IAssemblyProgram
+import prog8.code.core.IErrorReporter
+
+class CodeGen(internal val program: PtProgram,
+ internal val symbolTable: SymbolTable,
+ internal val options: CompilationOptions,
+ internal val errors: IErrorReporter
+): IAssemblyGenerator {
+
+ override fun compileToAssembly(): IAssemblyProgram? {
+
+ return AssemblyProgram(program.name)
+ }
+}
diff --git a/compiler/build.gradle b/compiler/build.gradle
index 1da302737..26f88d60c 100644
--- a/compiler/build.gradle
+++ b/compiler/build.gradle
@@ -32,6 +32,7 @@ dependencies {
implementation project(':codeOptimizers')
implementation project(':compilerAst')
implementation project(':codeGenCpu6502')
+ implementation project(':codeGenVirtual')
implementation project(':codeGenExperimental')
implementation 'org.antlr:antlr4-runtime:4.9.3'
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8"
diff --git a/compiler/compiler.iml b/compiler/compiler.iml
index ff5ec1887..943a0528b 100644
--- a/compiler/compiler.iml
+++ b/compiler/compiler.iml
@@ -22,5 +22,6 @@
+
\ No newline at end of file
diff --git a/compiler/res/prog8lib/virtual/syslib.p8 b/compiler/res/prog8lib/virtual/syslib.p8
new file mode 100644
index 000000000..a10c814cb
--- /dev/null
+++ b/compiler/res/prog8lib/virtual/syslib.p8
@@ -0,0 +1,151 @@
+; Prog8 definitions for the Virtual Machine
+;
+; Written by Irmen de Jong (irmen@razorvine.net) - license: GNU GPL 3.0
+;
+
+sys {
+ ; ------- lowlevel system routines --------
+
+ const ubyte target = 255 ; compilation target specifier. 64 = C64, 128 = C128, 16 = CommanderX16, 8 = atari800XL, 255 = virtual
+
+
+ sub reset_system() {
+ ; Soft-reset the system back to initial power-on Basic prompt.
+ ; TODO
+ }
+
+ sub wait(uword jiffies) {
+ ; --- wait approximately the given number of jiffies (1/60th seconds)
+ ; TODO
+ }
+
+ sub waitvsync() {
+ ; --- busy wait till the next vsync has occurred (approximately), without depending on custom irq handling.
+ ; TODO
+ }
+
+ sub memcopy(uword source, uword target, uword count) {
+ ; TODO
+ }
+
+ sub memset(uword mem, uword numbytes, ubyte value) {
+ ; TODO
+ }
+
+ sub memsetw(uword mem, uword numwords, uword value) {
+ ; TODO
+ }
+
+ sub exit(ubyte returnvalue) {
+ ; -- immediately exit the program with a return code in the A register
+ ; TODO
+ }
+}
+
+cx16 {
+
+ ; the sixteen virtual 16-bit registers that the CX16 has defined in the zeropage
+ ; they are simulated on the VirtualMachine as well but their location in memory is different
+; the sixteen virtual 16-bit registers in both normal unsigned mode and signed mode (s)
+ &uword r0 = $0002
+ &uword r1 = $0004
+ &uword r2 = $0006
+ &uword r3 = $0008
+ &uword r4 = $000a
+ &uword r5 = $000c
+ &uword r6 = $000e
+ &uword r7 = $0010
+ &uword r8 = $0012
+ &uword r9 = $0014
+ &uword r10 = $0016
+ &uword r11 = $0018
+ &uword r12 = $001a
+ &uword r13 = $001c
+ &uword r14 = $001e
+ &uword r15 = $0020
+
+ &word r0s = $0002
+ &word r1s = $0004
+ &word r2s = $0006
+ &word r3s = $0008
+ &word r4s = $000a
+ &word r5s = $000c
+ &word r6s = $000e
+ &word r7s = $0010
+ &word r8s = $0012
+ &word r9s = $0014
+ &word r10s = $0016
+ &word r11s = $0018
+ &word r12s = $001a
+ &word r13s = $001c
+ &word r14s = $001e
+ &word r15s = $0020
+
+ &ubyte r0L = $0002
+ &ubyte r1L = $0004
+ &ubyte r2L = $0006
+ &ubyte r3L = $0008
+ &ubyte r4L = $000a
+ &ubyte r5L = $000c
+ &ubyte r6L = $000e
+ &ubyte r7L = $0010
+ &ubyte r8L = $0012
+ &ubyte r9L = $0014
+ &ubyte r10L = $0016
+ &ubyte r11L = $0018
+ &ubyte r12L = $001a
+ &ubyte r13L = $001c
+ &ubyte r14L = $001e
+ &ubyte r15L = $0020
+
+ &ubyte r0H = $0003
+ &ubyte r1H = $0005
+ &ubyte r2H = $0007
+ &ubyte r3H = $0009
+ &ubyte r4H = $000b
+ &ubyte r5H = $000d
+ &ubyte r6H = $000f
+ &ubyte r7H = $0011
+ &ubyte r8H = $0013
+ &ubyte r9H = $0015
+ &ubyte r10H = $0017
+ &ubyte r11H = $0019
+ &ubyte r12H = $001b
+ &ubyte r13H = $001d
+ &ubyte r14H = $001f
+ &ubyte r15H = $0021
+
+ &byte r0sL = $0002
+ &byte r1sL = $0004
+ &byte r2sL = $0006
+ &byte r3sL = $0008
+ &byte r4sL = $000a
+ &byte r5sL = $000c
+ &byte r6sL = $000e
+ &byte r7sL = $0010
+ &byte r8sL = $0012
+ &byte r9sL = $0014
+ &byte r10sL = $0016
+ &byte r11sL = $0018
+ &byte r12sL = $001a
+ &byte r13sL = $001c
+ &byte r14sL = $001e
+ &byte r15sL = $0020
+
+ &byte r0sH = $0003
+ &byte r1sH = $0005
+ &byte r2sH = $0007
+ &byte r3sH = $0009
+ &byte r4sH = $000b
+ &byte r5sH = $000d
+ &byte r6sH = $000f
+ &byte r7sH = $0011
+ &byte r8sH = $0013
+ &byte r9sH = $0015
+ &byte r10sH = $0017
+ &byte r11sH = $0019
+ &byte r12sH = $001b
+ &byte r13sH = $001d
+ &byte r14sH = $001f
+ &byte r15sH = $0021
+}
diff --git a/compiler/res/prog8lib/virtual/textio.p8 b/compiler/res/prog8lib/virtual/textio.p8
new file mode 100644
index 000000000..80089b586
--- /dev/null
+++ b/compiler/res/prog8lib/virtual/textio.p8
@@ -0,0 +1,107 @@
+; Prog8 definitions for the Text I/O and Screen routines for the Virtual Machine
+;
+; Written by Irmen de Jong (irmen@razorvine.net) - license: GNU GPL 3.0
+
+%import syslib
+
+
+txt {
+
+const ubyte DEFAULT_WIDTH = 40
+const ubyte DEFAULT_HEIGHT = 24
+
+
+sub clear_screen() {
+ txt.chrout(125)
+}
+
+sub nl() {
+ txt.chrout('\n')
+}
+
+sub spc() {
+ txt.chrout(' ')
+}
+
+sub fill_screen (ubyte char) {
+ ; ---- fill the character screen with the given fill character.
+ ; TODO
+}
+
+sub clear_screenchars (ubyte char) {
+ ; ---- clear the character screen with the given fill character (leaves colors)
+ ; (assumes screen matrix is at the default address)
+ ; TODO
+}
+
+sub chrout(ubyte char) {
+ ; TODO
+}
+
+sub print (str text) {
+ ; ---- print null terminated string from A/Y
+ ; note: the compiler contains an optimization that will replace
+ ; a call to this subroutine with a string argument of just one char,
+ ; by just one call to CHROUT of that single char.
+ ; TODO
+}
+
+sub print_ub0 (ubyte value) {
+ ; ---- print the ubyte in A in decimal form, with left padding 0s (3 positions total)
+ ; TODO
+}
+
+sub print_ub (ubyte value) {
+ ; ---- print the ubyte in A in decimal form, without left padding 0s
+ ; TODO
+}
+
+sub print_b (byte value) {
+ ; ---- print the byte in A in decimal form, without left padding 0s
+ ; TODO
+}
+
+sub print_ubhex (ubyte value, ubyte prefix) {
+ ; ---- print the ubyte in A in hex form (if Carry is set, a radix prefix '$' is printed as well)
+ ; TODO
+}
+
+sub print_ubbin (ubyte value, ubyte prefix) {
+ ; ---- print the ubyte in A in binary form (if Carry is set, a radix prefix '%' is printed as well)
+ ; TODO
+}
+
+sub print_uwbin (uword value, ubyte prefix) {
+ ; ---- print the uword in A/Y in binary form (if Carry is set, a radix prefix '%' is printed as well)
+ ; TODO
+}
+
+sub print_uwhex (uword value, ubyte prefix) {
+ ; ---- print the uword in A/Y in hexadecimal form (4 digits)
+ ; (if Carry is set, a radix prefix '$' is printed as well)
+ ; TODO
+}
+
+sub print_uw0 (uword value) {
+ ; ---- print the uword in A/Y in decimal form, with left padding 0s (5 positions total)
+ ; TODO
+}
+
+sub print_uw (uword value) {
+ ; ---- print the uword in A/Y in decimal form, without left padding 0s
+ ; TODO
+}
+
+sub print_w (word value) {
+ ; ---- print the (signed) word in A/Y in decimal form, without left padding 0's
+ ; TODO
+}
+
+sub input_chars (uword buffer) -> ubyte {
+ ; ---- Input a string (max. 80 chars) from the keyboard. Returns length in Y. (string is terminated with a 0 byte as well)
+ ; It assumes the keyboard is selected as I/O channel!
+ ; TODO
+ return 0
+}
+
+}
diff --git a/compiler/src/prog8/CompilerMain.kt b/compiler/src/prog8/CompilerMain.kt
index d1a5bb969..4b5f7376a 100644
--- a/compiler/src/prog8/CompilerMain.kt
+++ b/compiler/src/prog8/CompilerMain.kt
@@ -3,10 +3,7 @@ package prog8
import kotlinx.cli.*
import prog8.ast.base.AstException
import prog8.code.core.CbmPrgLauncherType
-import prog8.code.target.AtariTarget
-import prog8.code.target.C128Target
-import prog8.code.target.C64Target
-import prog8.code.target.Cx16Target
+import prog8.code.target.*
import prog8.compiler.CompilationResult
import prog8.compiler.CompilerArguments
import prog8.compiler.compileProgram
@@ -46,8 +43,8 @@ private fun compileMain(args: Array): Boolean {
val quietAssembler by cli.option(ArgType.Boolean, fullName = "quietasm", description = "don't print assembler output results")
val asmListfile by cli.option(ArgType.Boolean, fullName = "asmlist", description = "make the assembler produce a listing file as well")
val experimentalCodegen by cli.option(ArgType.Boolean, fullName = "expericodegen", description = "use experimental codegen")
- 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}')").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 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 moduleFiles by cli.argument(ArgType.String, fullName = "modules", description = "main module file(s) to compile").multiple(999)
@@ -74,7 +71,7 @@ private fun compileMain(args: Array): Boolean {
if(srcdirs.firstOrNull()!=".")
srcdirs.add(0, ".")
- if (compilationTarget !in setOf(C64Target.NAME, C128Target.NAME, Cx16Target.NAME, AtariTarget.NAME)) {
+ if (compilationTarget !in setOf(C64Target.NAME, C128Target.NAME, Cx16Target.NAME, AtariTarget.NAME, VMTarget.NAME)) {
System.err.println("Invalid compilation target: $compilationTarget")
return false
}
diff --git a/compiler/src/prog8/compiler/Compiler.kt b/compiler/src/prog8/compiler/Compiler.kt
index 467e64659..c680e59d3 100644
--- a/compiler/src/prog8/compiler/Compiler.kt
+++ b/compiler/src/prog8/compiler/Compiler.kt
@@ -13,17 +13,13 @@ import prog8.ast.statements.VarDecl
import prog8.ast.walk.IAstVisitor
import prog8.code.SymbolTable
import prog8.code.core.*
-import prog8.code.target.AtariTarget
-import prog8.code.target.C128Target
-import prog8.code.target.C64Target
-import prog8.code.target.Cx16Target
+import prog8.code.target.*
import prog8.compiler.astprocessing.*
import prog8.optimizer.*
import prog8.parser.ParseError
import java.nio.file.Path
import kotlin.io.path.Path
import kotlin.io.path.nameWithoutExtension
-import kotlin.math.exp
import kotlin.math.round
import kotlin.system.measureTimeMillis
@@ -59,6 +55,7 @@ fun compileProgram(args: CompilerArguments): CompilationResult? {
C128Target.NAME -> C128Target()
Cx16Target.NAME -> Cx16Target()
AtariTarget.NAME -> AtariTarget()
+ VMTarget.NAME -> VMTarget()
else -> throw IllegalArgumentException("invalid compilation target")
}
@@ -444,13 +441,18 @@ internal fun asmGeneratorFor(program: Program,
if(options.experimentalCodegen) {
if (options.compTarget.machine.cpu in arrayOf(CpuType.CPU6502, CpuType.CPU65c02)) {
- // TODO for now, only use the new Intermediary Ast for this experimental codegen:
+ // TODO for now, use the new Intermediary Ast for this experimental codegen:
val intermediateAst = IntermediateAstMaker(program).transform()
- return prog8.codegen.experimental.AsmGen(intermediateAst, errors, symbolTable, options)
+ return prog8.codegen.experimental.AsmGen(intermediateAst, symbolTable, options, errors)
}
} else {
if (options.compTarget.machine.cpu in arrayOf(CpuType.CPU6502, CpuType.CPU65c02))
- return prog8.codegen.cpu6502.AsmGen(program, errors, symbolTable, options)
+ return prog8.codegen.cpu6502.AsmGen(program, symbolTable, options, errors)
+ if (options.compTarget.name == VMTarget.NAME) {
+ // TODO for now, use the new Intermediary Ast for this codegen:
+ val intermediateAst = IntermediateAstMaker(program).transform()
+ return prog8.codegen.virtual.CodeGen(intermediateAst, symbolTable, options, errors)
+ }
}
throw NotImplementedError("no asm generator for cpu ${options.compTarget.machine.cpu}")
diff --git a/compiler/test/codegeneration/TestAsmGenSymbols.kt b/compiler/test/codegeneration/TestAsmGenSymbols.kt
index 01a06a1e5..5c592f735 100644
--- a/compiler/test/codegeneration/TestAsmGenSymbols.kt
+++ b/compiler/test/codegeneration/TestAsmGenSymbols.kt
@@ -75,7 +75,7 @@ class TestAsmGenSymbols: StringSpec({
val options = CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.FULL, emptyList(), false, true, C64Target(), 999u)
options.compTarget.machine.zeropage = C64Zeropage(options)
val st = SymbolTableMaker().makeFrom(program)
- return AsmGen(program, errors, st, options)
+ return AsmGen(program, st, options, errors)
}
"symbol and variable names from strings" {
diff --git a/compiler/test/helpers/compileXyz.kt b/compiler/test/helpers/compileXyz.kt
index 4770aa7a6..c2bb6efd1 100644
--- a/compiler/test/helpers/compileXyz.kt
+++ b/compiler/test/helpers/compileXyz.kt
@@ -77,7 +77,7 @@ internal fun generateAssembly(
val errors = ErrorReporterForTests()
determineProgramLoadAddress(program, coptions, errors)
errors.report()
- val asmgen = AsmGen(program, errors, st, coptions)
+ val asmgen = AsmGen(program, st, coptions, errors)
errors.report()
return asmgen.compileToAssembly()
}
diff --git a/examples/test.p8 b/examples/test.p8
index 1029bf448..d1da8444d 100644
--- a/examples/test.p8
+++ b/examples/test.p8
@@ -1,14 +1,6 @@
%import textio
-%import test_stack
-%zeropage basicsafe
-%zpreserved 50,80
-%zpreserved 150,155
-%option align_word
-
main {
- %option align_word
-
ubyte[256] sieve
ubyte candidate_prime = 2 ; is increased in the loop
diff --git a/settings.gradle b/settings.gradle
index d0da69186..0e67d8fa3 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -4,6 +4,8 @@ include(
':codeAst',
':compilerAst',
':codeOptimizers',
+ ':virtualmachine',
+ ':codeGenVirtual',
':codeGenCpu6502',
':codeGenExperimental',
':compiler',
diff --git a/virtualmachine/build.gradle b/virtualmachine/build.gradle
new file mode 100644
index 000000000..febb4f22b
--- /dev/null
+++ b/virtualmachine/build.gradle
@@ -0,0 +1,42 @@
+
+plugins {
+ id 'java'
+ id 'application'
+ id "org.jetbrains.kotlin.jvm"
+}
+
+java {
+ toolchain {
+ languageVersion = JavaLanguageVersion.of(javaVersion)
+ }
+}
+
+compileKotlin {
+ kotlinOptions {
+ jvmTarget = javaVersion
+ }
+}
+
+compileTestKotlin {
+ kotlinOptions {
+ jvmTarget = javaVersion
+ }
+}
+
+dependencies {
+ implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8"
+ implementation "com.michael-bull.kotlin-result:kotlin-result-jvm:1.1.14"
+}
+
+sourceSets {
+ main {
+ java {
+ srcDirs = ["${project.projectDir}/src"]
+ }
+ resources {
+ srcDirs = ["${project.projectDir}/res"]
+ }
+ }
+}
+
+// note: there are no unit tests in this module!
diff --git a/virtualmachine/src/prog8/vm/Assembler.kt b/virtualmachine/src/prog8/vm/Assembler.kt
new file mode 100644
index 000000000..c2053b2d4
--- /dev/null
+++ b/virtualmachine/src/prog8/vm/Assembler.kt
@@ -0,0 +1,214 @@
+package prog8.vm
+
+
+class Assembler {
+ private val labels = mutableMapOf()
+ private val placeholders = mutableMapOf()
+
+ init {
+ require(instructionFormats.size== Opcode.values().size)
+ }
+
+ fun initializeMemory(memsrc: String, memory: Memory) {
+ val instrPattern = Regex("""(.+?)\s+([a-z]+)\s+(.+)""", RegexOption.IGNORE_CASE)
+ for(line in memsrc.lines()) {
+ if(line.isBlank())
+ continue
+ val match = instrPattern.matchEntire(line.trim())
+ if(match==null)
+ throw IllegalArgumentException("invalid line $line")
+ else {
+ val (_, addr, what, values) = match.groupValues
+ var address = parseValue(addr, 0)
+ when(what) {
+ "str" -> {
+ val string = unescape(values.trim('"'))
+ memory.setString(address, string, false)
+ }
+ "strz" -> {
+ val string = unescape(values.trim('"'))
+ memory.setString(address, string, true)
+ }
+ "ubyte" -> {
+ val array = values.split(',').map { parseValue(it.trim(), 0) }
+ for (value in array) {
+ memory.setB(address, value.toUByte())
+ address++
+ }
+ }
+ "uword" -> {
+ val array = values.split(',').map { parseValue(it.trim(), 0) }
+ for (value in array) {
+ memory.setW(address, value.toUShort())
+ address++
+ }
+ }
+ else -> throw IllegalArgumentException("mem instr $what")
+ }
+ }
+ }
+ }
+
+ fun assembleProgram(source: CharSequence): List {
+ labels.clear()
+ placeholders.clear()
+ val program = mutableListOf()
+ val instructionPattern = Regex("""([a-z]+)(\.b|\.w)?(.*)""", RegexOption.IGNORE_CASE)
+ val labelPattern = Regex("""_([a-z0-9]+):""")
+ for (line in source.lines()) {
+ if(line.isBlank() || line.startsWith(';'))
+ continue
+ val match = instructionPattern.matchEntire(line.trim())
+ if(match==null) {
+ 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 labels)
+ throw IllegalArgumentException("label redefined $label")
+ labels[label] = program.size
+ }
+ } else {
+ val (_, instr, typestr, rest) = match.groupValues
+ val opcode = Opcode.valueOf(instr.uppercase())
+ var type: DataType? = convertType(typestr)
+ val operands = rest.lowercase().split(",").toMutableList()
+ var reg1: Int? = null
+ var reg2: Int? = null
+ var reg3: Int? = null
+ var value: Int? = null
+ var operand: String?
+ if(operands.isNotEmpty() && operands[0].isNotEmpty()) {
+ operand = operands.removeFirst().trim()
+ if(operand[0]=='r')
+ reg1 = operand.substring(1).toInt()
+ else {
+ value = parseValue(operand, program.size)
+ operands.clear()
+ }
+ if(operands.isNotEmpty()) {
+ operand = operands.removeFirst().trim()
+ if(operand[0]=='r')
+ reg2 = operand.substring(1).toInt()
+ else {
+ value = parseValue(operand, program.size)
+ operands.clear()
+ }
+ if(operands.isNotEmpty()) {
+ operand = operands.removeFirst().trim()
+ if(operand[0]=='r')
+ reg3 = operand.substring(1).toInt()
+ else {
+ value = parseValue(operand, program.size)
+ operands.clear()
+ }
+ if(operands.isNotEmpty()) {
+ operand = operands.removeFirst().trim()
+ value = parseValue(operand, program.size)
+ operands.clear()
+ }
+ }
+ }
+ }
+ val format = instructionFormats.getValue(opcode)
+ if(type==null && format.datatypes.isNotEmpty())
+ type= DataType.BYTE
+ if(type!=null && type !in format.datatypes)
+ 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.reg3 && reg3==null)
+ throw IllegalArgumentException("needs reg3 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(!format.reg3 && reg3!=null)
+ throw IllegalArgumentException("invalid reg3 for $line")
+ if(!format.value && value!=null)
+ throw IllegalArgumentException("invalid value for $line")
+ program.add(Instruction(opcode, type, reg1, reg2, reg3, value))
+ }
+ }
+
+ pass2replaceLabels(program)
+ return program
+ }
+
+ private fun pass2replaceLabels(program: MutableList) {
+ for((line, label) in placeholders) {
+ val replacement = labels.getValue(label)
+ program[line] = program[line].copy(value = replacement)
+ }
+ }
+
+ private fun parseValue(value: String, pc: Int): Int {
+ if(value.startsWith('$'))
+ return value.substring(1).toInt(16)
+ if(value.startsWith('%'))
+ return value.substring(1).toInt(2)
+ if(value.startsWith("0x"))
+ return value.substring(2).toInt(16)
+ if(value.startsWith('_')) {
+ placeholders[pc] = value.substring(1)
+ return 0
+ }
+ return value.toInt()
+ }
+
+ private fun convertType(typestr: String): DataType? {
+ return when(typestr.lowercase()) {
+ "" -> null
+ ".b" -> DataType.BYTE
+ ".w" -> DataType.WORD
+ else -> throw IllegalArgumentException("invalid type $typestr")
+ }
+ }
+
+ private fun unescape(str: String): String {
+ val result = mutableListOf()
+ val iter = str.iterator()
+ while(iter.hasNext()) {
+ val c = iter.nextChar()
+ if(c=='\\') {
+ val ec = iter.nextChar()
+ result.add(when(ec) {
+ '\\' -> '\\'
+ 'n' -> '\n'
+ 'r' -> '\r'
+ '"' -> '"'
+ '\'' -> '\''
+ 'u' -> {
+ try {
+ "${iter.nextChar()}${iter.nextChar()}${iter.nextChar()}${iter.nextChar()}".toInt(16).toChar()
+ } catch (sb: StringIndexOutOfBoundsException) {
+ throw IllegalArgumentException("invalid \\u escape sequence")
+ } catch (nf: NumberFormatException) {
+ throw IllegalArgumentException("invalid \\u escape sequence")
+ }
+ }
+ 'x' -> {
+ // special hack 0x8000..0x80ff will be outputted verbatim without encoding
+ try {
+ val hex = ("" + iter.nextChar() + iter.nextChar()).toInt(16)
+ (0x8000 + hex).toChar()
+ } catch (sb: StringIndexOutOfBoundsException) {
+ throw IllegalArgumentException("invalid \\x escape sequence")
+ } catch (nf: NumberFormatException) {
+ throw IllegalArgumentException("invalid \\x escape sequence")
+ }
+ }
+ else -> throw IllegalArgumentException("invalid escape char in string: \\$ec")
+ })
+ } else {
+ result.add(c)
+ }
+ }
+ return result.joinToString("")
+ }
+}
diff --git a/virtualmachine/src/prog8/vm/GraphicsWindow.kt b/virtualmachine/src/prog8/vm/GraphicsWindow.kt
new file mode 100644
index 000000000..1a8952ce4
--- /dev/null
+++ b/virtualmachine/src/prog8/vm/GraphicsWindow.kt
@@ -0,0 +1,86 @@
+package prog8.vm
+
+import java.awt.*
+import java.awt.image.BufferedImage
+import javax.swing.JFrame
+import javax.swing.JPanel
+import javax.swing.Timer
+import kotlin.math.max
+import kotlin.math.min
+
+
+class GraphicsWindow(val pixelWidth: Int, val pixelHeight: Int, val pixelScaling: Int): JFrame("vm-graphics $pixelWidth × $pixelHeight"), AutoCloseable {
+ private lateinit var repaintTimer: Timer
+
+ fun start() {
+ val refreshRate = optimalRefreshRate(this)
+ repaintTimer = Timer(1000/refreshRate) {
+ repaint()
+ }
+ repaintTimer.initialDelay = 0
+ repaintTimer.start()
+ }
+
+ override fun close() {
+ repaintTimer.stop()
+ dispose()
+ }
+
+ val image: BufferedImage
+
+ init {
+ contentPane.layout = BorderLayout()
+ background = Color.BLACK
+ contentPane.background = Color.BLACK
+ isResizable = false
+ isLocationByPlatform = true
+ defaultCloseOperation = JFrame.EXIT_ON_CLOSE
+ image = graphicsConfiguration.createCompatibleImage(pixelWidth, pixelHeight, Transparency.OPAQUE)
+ contentPane.add(BitmapScreenPanel(image, pixelScaling), BorderLayout.CENTER)
+ pack()
+ requestFocusInWindow()
+ isVisible = true
+ }
+
+ private fun optimalRefreshRate(frame: JFrame): Int {
+ var rate = frame.graphicsConfiguration.device.displayMode.refreshRate
+ if(rate==0)
+ rate = GraphicsEnvironment.getLocalGraphicsEnvironment().screenDevices.map { it.displayMode.refreshRate }.first { it>0 }
+ return max(30, min(250, rate))
+ }
+
+ fun clear(color: Int) {
+ val g2d = image.graphics as Graphics2D
+ g2d.background = Color(color, color, color)
+ g2d.clearRect(0,0, pixelWidth*pixelScaling, pixelHeight*pixelScaling)
+ g2d.dispose()
+ }
+
+ fun plot(x: Int, y: Int, color: Int) {
+ if(x<0 || x>=pixelWidth)
+ throw IllegalArgumentException("plot x outside of screen: $x")
+ if(y<0 || y>=pixelHeight)
+ throw IllegalArgumentException("plot y outside of screen: $y")
+ image.setRGB(x, y, Color(color, color, color, 255).rgb)
+ }
+}
+
+
+internal class BitmapScreenPanel(private val drawImage: BufferedImage, val pixelScaling: Int) : JPanel() {
+ init {
+ val size = Dimension(drawImage.width * pixelScaling, drawImage.height * pixelScaling)
+ minimumSize = size
+ maximumSize = size
+ preferredSize = size
+ isFocusable = true
+ isDoubleBuffered = false
+ requestFocusInWindow()
+ }
+
+ override fun paint(graphics: Graphics) {
+ val g2d = graphics as Graphics2D
+ g2d.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR)
+ g2d.drawImage(drawImage, 0, 0, size.width, size.height, null)
+ Toolkit.getDefaultToolkit().sync()
+ }
+}
diff --git a/virtualmachine/src/prog8/vm/Instructions.kt b/virtualmachine/src/prog8/vm/Instructions.kt
new file mode 100644
index 000000000..4956604f6
--- /dev/null
+++ b/virtualmachine/src/prog8/vm/Instructions.kt
@@ -0,0 +1,250 @@
+package prog8.vm
+
+/*
+
+65536 virtual registers, maximum 16 bits wide.
+65536 bytes of memory.
+So a memory pointer is also limited to 16 bits.
+
+
+TODO: also make a 3-address-code instructionset?
+
+
+Instruction serialization format possibility:
+
+OPCODE: 1 byte
+TYPECODE: 1 byte
+REGISTER 1: 2 bytes
+REGISTER 2: 2 bytes
+REG3/MEMORY/VALUE: 2 bytes
+
+Instructions with Type come in variants b/w/f (omitting it in the instruction means '8' if instruction otherwise has a T)
+Currently NO support for 24 or 32 bits, and FLOATING POINT is not implemented yet either.
+
+*only* LOAD AND STORE instructions have a possible memory operand, all other instructions use only registers or immediate value.
+
+
+LOAD/STORE -- all have type b/w/f. (note: float not yet implemented)
+
+
+load reg1, value - load immediate value into register
+loadm reg1, value - load reg1 with value in memory address given by value
+loadi reg1, reg2 - load reg1 with value in memory indirect, memory pointed to by reg2
+loadx reg1, reg2, value - load reg1 with value in memory address given by value, indexed by value in reg2
+loadr reg1, reg2 - load reg1 with value in register reg2
+swapreg reg1, reg2 - swap values in reg1 and reg2
+
+storem reg1, value - store reg1 in memory address given by value
+storei reg1, reg2 - store reg1 in memory indirect, memory pointed to by reg2
+storex reg1, reg2, value - store reg1 in memory address given by value, indexed by value in reg2
+storez value - store zero in memory given by value
+storezi reg1 - store zero in memory pointed to by reg
+storezx reg1, value - store zero in memory given by value, indexed by value in reg
+
+
+FLOW CONTROL
+
+Subroutine call convention:
+Subroutine parameters set in Reg 0, 1, 2... before gosub.
+Return value in Reg 0 before return.
+
+jump location - continue running at instruction number given by location
+jumpi reg1 - continue running at instruction number given by reg1
+gosub location - save current instruction location+1, continue execution at location
+gosubi reg1 - gosub to subroutine at instruction number given by reg1
+syscall value - do a systemcall identified by call number
+return - restore last saved instruction location and continue at that instruction
+
+branch instructions have b/w/f types (f not implemented)
+bz reg1, value - branch if reg1 is zero
+bnz reg1, value - branch if reg1 is not zero
+beq reg1, reg2, value - jump to location in program given by value, if reg1 == reg2
+bne reg1, reg2, value - jump to location in program given by value, if reg1 != reg2
+blt reg1, reg2, value - jump to location in program given by value, if reg1 < reg2 (unsigned)
+blts reg1, reg2, value - jump to location in program given by value, if reg1 < reg2 (signed)
+bgt reg1, reg2, value - jump to location in program given by value, if reg1 > reg2 (unsigned)
+bgts reg1, reg2, value - jump to location in program given by value, if reg1 > reg2 (signed)
+ble reg1, reg2, value - jump to location in program given by value, if reg1 <= reg2 (unsigned)
+bles reg1, reg2, value - jump to location in program given by value, if reg1 <= reg2 (signed)
+bge reg1, reg2, value - jump to location in program given by value, if reg1 >= reg2 (unsigned)
+bges reg1, reg2, value - jump to location in program given by value, if reg1 >= reg2 (signed)
+
+
+ARITHMETIC - all have a type of b/w/f. (note: float not yet implemented)
+(note: for calculations, all types -result, and both operands- are identical)
+
+neg reg1, reg2 - reg1 = sign negation of reg2
+add reg1, reg2, reg3 - reg1 = reg2+reg3 (unsigned + signed)
+addi reg1, reg2, value - reg1 = reg2+value (unsigned + signed)
+sub reg1, reg2, reg3 - reg1 = reg2-reg3 (unsigned + signed)
+subi reg1, reg2, value - reg1 = reg2-value (unsigned + signed)
+ext reg1, reg2 - reg1 = unsigned extension of reg2 (which in practice just means clearing the MSB / MSW) (latter not yet implemented as we don't have longs yet)
+exts reg1, reg2 - reg1 = signed extension of reg2 (byte to word, or word to long) (note: latter ext.w, not yet implemented as we don't have longs yet)
+mul reg1, reg2, reg3 - unsigned multiply reg1=reg2*reg3 note: byte*byte->byte, no type extension to word!
+div reg1, reg2, reg3 - unsigned division reg1=reg2/reg3 note: division by zero yields max signed int $ff/$ffff
+
+
+LOGICAL/BITWISE - all have a type of b/w. but never f
+inv reg1, reg2 - reg1 = bitwise invert of reg2
+and reg1, reg2, reg3 - reg1 = reg2 bitwise and reg3
+or reg1, reg2, reg3 - reg1 = reg2 bitwise or reg3
+xor reg1, reg2, reg3 - reg1 = reg2 bitwise xor reg3
+lsr reg1, reg2 - reg1 = shift reg2 right by 1 bit
+lsl reg1, reg2 - reg1 = shift reg2 left by 1 bit
+ror reg1, reg2 - reg1 = rotate reg2 right by 1 bit, not using carry
+rol reg1, reg2 - reg1 = rotate reg2 left by 1 bit, not using carry
+
+Not sure if the variants using the carry bit should be added, for the ror/rol instructions.
+These do map directly on 6502 and 68k instructions though.
+
+
+MISC
+
+nop - do nothing
+breakpoint - trigger a breakpoint
+copy reg1, reg2, length - copy memory from ptrs in reg1 to reg3, length bytes
+copyz reg1, reg2 - copy memory from ptrs in reg1 to reg3, stop after first 0-byte
+swap [b, w] reg1, reg2 - reg1 = swapped lsb and msb from register reg2 (16 bits) or lsw and msw (32 bits)
+
+
+ */
+
+enum class Opcode {
+ NOP,
+ LOAD,
+ LOADM,
+ LOADI,
+ LOADX,
+ LOADR,
+ SWAPREG,
+ STOREM,
+ STOREI,
+ STOREX,
+ STOREZ,
+ STOREZI,
+ STOREZX,
+
+ JUMP,
+ JUMPI,
+ GOSUB,
+ GOSUBI,
+ SYSCALL,
+ RETURN,
+ BZ,
+ BNZ,
+ BEQ,
+ BNE,
+ BLT,
+ BLTS,
+ BGT,
+ BGTS,
+ BLE,
+ BLES,
+ BGE,
+ BGES,
+
+ NEG,
+ ADD,
+ ADDI,
+ SUB,
+ SUBI,
+ MUL,
+ DIV,
+ EXT,
+ EXTS,
+
+ INV,
+ AND,
+ OR,
+ XOR,
+ LSR,
+ LSL,
+ ROR,
+ ROL,
+
+ COPY,
+ COPYZ,
+ SWAP,
+ BREAKPOINT
+}
+
+enum class DataType {
+ BYTE,
+ WORD
+ // TODO add LONG? FLOAT?
+}
+
+data class Instruction(
+ val opcode: Opcode,
+ val type: DataType?=null,
+ val reg1: Int?=null, // 0-$ffff
+ val reg2: Int?=null, // 0-$ffff
+ val reg3: Int?=null, // 0-$ffff
+ val value: Int?=null, // 0-$ffff
+)
+
+data class InstructionFormat(val datatypes: Set, val reg1: Boolean, val reg2: Boolean, val reg3: Boolean, val value: Boolean)
+
+private val NN = emptySet()
+private val BW = setOf(DataType.BYTE, DataType.WORD)
+
+@Suppress("BooleanLiteralArgument")
+val instructionFormats = mutableMapOf(
+ // opcode to types, reg1, reg2, reg3, value
+ Opcode.NOP to InstructionFormat(NN, false, false, false, false),
+ Opcode.LOAD to InstructionFormat(BW, true, false, false, true ),
+ Opcode.LOADM to InstructionFormat(BW, true, false, false, true ),
+ Opcode.LOADI to InstructionFormat(BW, true, true, false, false),
+ Opcode.LOADX to InstructionFormat(BW, true, true, false, true ),
+ Opcode.LOADR to InstructionFormat(BW, true, true, false, false),
+ Opcode.SWAPREG to InstructionFormat(BW, true, true, false, false),
+ Opcode.STOREM to InstructionFormat(BW, true, false, false, true ),
+ Opcode.STOREI to InstructionFormat(BW, true, true, false, false),
+ Opcode.STOREX to InstructionFormat(BW, true, true, false, true ),
+ Opcode.STOREZ to InstructionFormat(BW, false, false, false, true ),
+ Opcode.STOREZI to InstructionFormat(BW, true, false, false, false),
+ Opcode.STOREZX to InstructionFormat(BW, true, false, false, true ),
+
+ Opcode.JUMP to InstructionFormat(NN, false, false, false, true ),
+ Opcode.JUMPI to InstructionFormat(NN, true, false, false, false),
+ Opcode.GOSUB to InstructionFormat(NN, false, false, false, true ),
+ Opcode.GOSUBI to InstructionFormat(NN, true, false, false, false),
+ Opcode.SYSCALL to InstructionFormat(NN, false, false, false, true ),
+ Opcode.RETURN to InstructionFormat(NN, false, false, false, false),
+ Opcode.BZ to InstructionFormat(BW, true, false, false, true ),
+ Opcode.BNZ to InstructionFormat(BW, true, false, false, true ),
+ Opcode.BEQ to InstructionFormat(BW, true, true, false, true ),
+ Opcode.BNE to InstructionFormat(BW, true, true, false, true ),
+ Opcode.BLT to InstructionFormat(BW, true, true, false, true ),
+ Opcode.BLTS to InstructionFormat(BW, true, true, false, true ),
+ Opcode.BGT to InstructionFormat(BW, true, true, false, true ),
+ Opcode.BGTS to InstructionFormat(BW, true, true, false, true ),
+ Opcode.BLE to InstructionFormat(BW, true, true, false, true ),
+ Opcode.BLES to InstructionFormat(BW, true, true, false, true ),
+ Opcode.BGE to InstructionFormat(BW, true, true, false, true ),
+ Opcode.BGES to InstructionFormat(BW, true, true, false, true ),
+
+ Opcode.NEG to InstructionFormat(BW, true, true, false, false),
+ Opcode.ADD to InstructionFormat(BW, true, true, true, false),
+ Opcode.ADDI to InstructionFormat(BW, true, true, false, true ),
+ Opcode.SUB to InstructionFormat(BW, true, true, true, false),
+ Opcode.SUBI to InstructionFormat(BW, true, true, false, true ),
+ Opcode.MUL to InstructionFormat(BW, true, true, true, false),
+ Opcode.DIV to InstructionFormat(BW, true, true, true, false),
+ Opcode.EXT to InstructionFormat(BW, true, true, false, false),
+ Opcode.EXTS to InstructionFormat(BW, true, true, false, false),
+
+ Opcode.INV to InstructionFormat(BW, true, true, false, false),
+ Opcode.AND to InstructionFormat(BW, true, true, true, false),
+ Opcode.OR to InstructionFormat(BW, true, true, true, false),
+ Opcode.XOR to InstructionFormat(BW, true, true, true, false),
+ Opcode.LSR to InstructionFormat(BW, true, true, false, false),
+ Opcode.LSL to InstructionFormat(BW, true, true, false, false),
+ Opcode.ROR to InstructionFormat(BW, true, true, false, false),
+ Opcode.ROL to InstructionFormat(BW, true, true, false, false),
+
+ Opcode.COPY to InstructionFormat(NN, true, true, false, true ),
+ Opcode.COPYZ to InstructionFormat(NN, true, true, false, false),
+ Opcode.SWAP to InstructionFormat(BW, true, true, false, false),
+ Opcode.BREAKPOINT to InstructionFormat(NN, false, false, false, false)
+)
diff --git a/virtualmachine/src/prog8/vm/Main.kt b/virtualmachine/src/prog8/vm/Main.kt
new file mode 100644
index 000000000..2082f0a78
--- /dev/null
+++ b/virtualmachine/src/prog8/vm/Main.kt
@@ -0,0 +1,43 @@
+package prog8.vm
+
+fun main(args: Array) {
+ 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)
+ vm.run()
+}
diff --git a/virtualmachine/src/prog8/vm/Memory.kt b/virtualmachine/src/prog8/vm/Memory.kt
new file mode 100644
index 000000000..d8917d587
--- /dev/null
+++ b/virtualmachine/src/prog8/vm/Memory.kt
@@ -0,0 +1,65 @@
+package prog8.vm
+
+/**
+ * 64 Kb of random access memory.
+ */
+class Memory {
+ private val mem = Array(64 * 1024) { 0u }
+
+ fun reset() {
+ mem.fill(0u)
+ }
+
+ fun getB(address: Int): UByte {
+ return mem[address]
+ }
+
+ fun setB(address: Int, value: UByte) {
+ mem[address] = value
+ }
+
+ fun getW(address: Int): UShort {
+ return (256u*mem[address] + mem[address+1]).toUShort()
+ }
+
+ fun setW(address: Int, value: UShort) {
+ mem[address] = (value.toInt() ushr 8).toUByte()
+ mem[address+1] = value.toUByte()
+ }
+
+ // for now, no LONG 32-bits and no FLOAT support.
+// fun getL(address: Int): UInt {
+// return mem[address+3] + 256u*mem[address+2] + 65536u*mem[address+1] + 16777216u*mem[address]
+// }
+//
+// fun setL(address: Int, value: UInt) {
+// val v = value.toInt()
+// mem[address] = (v ushr 24).toUByte()
+// mem[address+1] = (v ushr 16).toUByte()
+// mem[address+2] = (v ushr 8).toUByte()
+// mem[address+3] = v.toUByte()
+// }
+
+ fun setString(address: Int, string: String, zeroTerminated: Boolean) {
+ var addr=address
+ for (c in string) {
+ mem[addr] = c.code.toUByte()
+ addr++
+ }
+ if(zeroTerminated)
+ mem[addr] = 0u
+ }
+
+ fun getString(address: Int): String {
+ val sb = StringBuilder()
+ var addr = address
+ while(true) {
+ val char = mem[addr].toInt()
+ if(char==0)
+ break
+ sb.append(Char(char))
+ addr++
+ }
+ return sb.toString()
+ }
+}
\ No newline at end of file
diff --git a/virtualmachine/src/prog8/vm/Registers.kt b/virtualmachine/src/prog8/vm/Registers.kt
new file mode 100644
index 000000000..a404019e4
--- /dev/null
+++ b/virtualmachine/src/prog8/vm/Registers.kt
@@ -0,0 +1,24 @@
+package prog8.vm
+
+/**
+ * 65536 virtual registers of 16 bits wide.
+ */
+class Registers {
+ private val registers = Array(65536) { 0u }
+
+ fun reset() {
+ registers.fill(0u)
+ }
+
+ fun setB(reg: Int, value: UByte) {
+ registers[reg] = registers[reg] and 0xff00u or value.toUShort()
+ }
+
+ fun setW(reg: Int, value: UShort) {
+ registers[reg] = value
+ }
+
+ fun getB(reg: Int) = registers[reg].toUByte()
+
+ fun getW(reg: Int) = registers[reg]
+}
\ No newline at end of file
diff --git a/virtualmachine/src/prog8/vm/SysCalls.kt b/virtualmachine/src/prog8/vm/SysCalls.kt
new file mode 100644
index 000000000..0bf05379f
--- /dev/null
+++ b/virtualmachine/src/prog8/vm/SysCalls.kt
@@ -0,0 +1,81 @@
+package prog8.vm
+
+import kotlin.math.min
+
+/*
+SYSCALLS:
+
+0 = reset ; resets system
+1 = exit ; stops program and returns statuscode from r0.w
+2 = print_c ; print single character
+3 = print_s ; print 0-terminated string from memory
+4 = print_u8 ; print unsigned int byte
+5 = print_u16 ; print unsigned int word
+6 = input ; reads a line of text entered by the user, r0.w = memory buffer, r1.b = maxlength (0-255, 0=unlimited). Zero-terminates the string. Returns length in r65535.w
+7 = sleep ; sleep amount of milliseconds
+8 = gfx_enable ; enable graphics window r0.b = 0 -> lores 320x240, r0.b = 1 -> hires 640x480
+9 = gfx_clear ; clear graphics window with shade in r0.b
+10 = gfx_plot ; plot pixel in graphics window, r0.w/r1.w contain X and Y coordinates, r2.b contains brightness
+*/
+
+enum class Syscall {
+ RESET,
+ EXIT,
+ PRINT_C,
+ PRINT_S,
+ PRINT_U8,
+ PRINT_U16,
+ INPUT,
+ SLEEP,
+ GFX_ENABLE,
+ GFX_CLEAR,
+ GFX_PLOT
+}
+
+object SysCalls {
+ fun call(call: Syscall, vm: VirtualMachine) {
+ when(call) {
+ Syscall.RESET -> {
+ vm.reset()
+ }
+ Syscall.EXIT ->{
+ vm.exit()
+ }
+ Syscall.PRINT_C -> {
+ val char = vm.registers.getB(0).toInt()
+ print(Char(char))
+ }
+ Syscall.PRINT_S -> {
+ var addr = vm.registers.getW(0).toInt()
+ while(true) {
+ val char = vm.memory.getB(addr).toInt()
+ if(char==0)
+ break
+ print(Char(char))
+ addr++
+ }
+ }
+ Syscall.PRINT_U8 -> {
+ print(vm.registers.getB(0))
+ }
+ Syscall.PRINT_U16 -> {
+ print(vm.registers.getW(0))
+ }
+ Syscall.INPUT -> {
+ var input = readln()
+ val maxlen = vm.registers.getB(1).toInt()
+ if(maxlen>0)
+ input = input.substring(0, min(input.length, maxlen))
+ vm.memory.setString(vm.registers.getW(0).toInt(), input, true)
+ vm.registers.setW(65535, input.length.toUShort())
+ }
+ Syscall.SLEEP -> {
+ val duration = vm.registers.getW(0).toLong()
+ Thread.sleep(duration)
+ }
+ Syscall.GFX_ENABLE -> vm.gfx_enable()
+ Syscall.GFX_CLEAR -> vm.gfx_clear()
+ Syscall.GFX_PLOT -> vm.gfx_plot()
+ }
+ }
+}
diff --git a/virtualmachine/src/prog8/vm/VirtualMachine.kt b/virtualmachine/src/prog8/vm/VirtualMachine.kt
new file mode 100644
index 000000000..30a285ef2
--- /dev/null
+++ b/virtualmachine/src/prog8/vm/VirtualMachine.kt
@@ -0,0 +1,647 @@
+package prog8.vm
+
+import java.util.*
+import kotlin.math.roundToInt
+
+
+class ProgramExitException(val status: Int): Exception()
+
+
+class BreakpointException(val pc: Int): Exception()
+
+
+@Suppress("FunctionName")
+class VirtualMachine(val memory: Memory, program: List) {
+ val registers = Registers()
+ val program: Array = program.toTypedArray()
+ val callStack = Stack()
+ var pc = 0
+ var stepCount = 0
+
+ init {
+ if(program.size>65536)
+ throw IllegalArgumentException("program cannot contain more than 65536 instructions")
+ }
+
+ fun run() {
+ try {
+ var before = System.nanoTime()
+ var numIns = 0
+ while(true) {
+ step()
+ numIns++
+
+ if(stepCount and 32767 == 0) {
+ Thread.sleep(0, 10) // avoid 100% cpu core usage
+ }
+
+ if(stepCount and 0xffffff == 0) {
+ val now = System.nanoTime()
+ val duration = now-before
+ before = now
+ val insPerSecond = numIns*1000.0/duration
+ println("${insPerSecond.roundToInt()} MIPS")
+ numIns = 0
+ }
+ }
+ } catch (hx: ProgramExitException) {
+ println("\nProgram exit! Statuscode=${hx.status} #steps=${stepCount}")
+ gfx_close()
+ }
+ }
+
+ fun reset() {
+ registers.reset()
+ memory.reset()
+ pc = 0
+ stepCount = 0
+ callStack.clear()
+ }
+
+ fun exit() {
+ throw ProgramExitException(registers.getW(0).toInt())
+ }
+
+ fun step(count: Int=1) {
+ var left=count
+ while(left>0) {
+ stepCount++
+ dispatch()
+ left--
+ }
+ }
+
+ private fun dispatch() {
+ if(pc >= program.size)
+ exit()
+ val ins = program[pc]
+ when(ins.opcode) {
+ Opcode.NOP -> { pc++ }
+ Opcode.LOAD -> InsLOAD(ins)
+ Opcode.LOADM -> InsLOADM(ins)
+ Opcode.LOADX -> InsLOADX(ins)
+ Opcode.LOADI -> InsLOADI(ins)
+ Opcode.LOADR -> InsLOADR(ins)
+ Opcode.SWAPREG -> InsSWAPREG(ins)
+ Opcode.STOREM -> InsSTOREM(ins)
+ Opcode.STOREX -> InsSTOREX(ins)
+ Opcode.STOREI -> InsSTOREI(ins)
+ Opcode.STOREZ -> InsSTOREZ(ins)
+ Opcode.STOREZX -> InsSTOREZX(ins)
+ Opcode.STOREZI -> InsSTOREZI(ins)
+ Opcode.JUMP -> InsJUMP(ins)
+ Opcode.JUMPI -> InsJUMPI(ins)
+ Opcode.GOSUB -> InsGOSUB(ins)
+ Opcode.GOSUBI -> InsGOSUBI(ins)
+ Opcode.SYSCALL -> InsSYSCALL(ins)
+ Opcode.RETURN -> InsRETURN()
+ Opcode.BZ -> InsBZ(ins)
+ Opcode.BNZ -> InsBNZ(ins)
+ Opcode.BEQ -> InsBEQ(ins)
+ Opcode.BNE -> InsBNE(ins)
+ Opcode.BLT -> InsBLTU(ins)
+ Opcode.BLTS -> InsBLTS(ins)
+ Opcode.BGT -> InsBGTU(ins)
+ Opcode.BGTS -> InsBGTS(ins)
+ Opcode.BLE -> InsBLEU(ins)
+ Opcode.BLES -> InsBLES(ins)
+ Opcode.BGE -> InsBGEU(ins)
+ Opcode.BGES -> InsBGES(ins)
+ Opcode.NEG -> InsNEG(ins)
+ Opcode.ADD -> InsADD(ins)
+ Opcode.ADDI -> InsADDI(ins)
+ Opcode.SUB -> InsSUB(ins)
+ Opcode.SUBI -> InsSUBI(ins)
+ Opcode.MUL -> InsMul(ins)
+ Opcode.DIV -> InsDiv(ins)
+ Opcode.EXT -> InsEXT(ins)
+ Opcode.EXTS -> InsEXTS(ins)
+ Opcode.INV -> InsINV(ins)
+ Opcode.AND -> InsAND(ins)
+ Opcode.OR -> InsOR(ins)
+ Opcode.XOR -> InsXOR(ins)
+ Opcode.LSR -> InsLSR(ins)
+ Opcode.LSL -> InsLSL(ins)
+ Opcode.ROR -> InsROR(ins)
+ Opcode.ROL -> InsROL(ins)
+ Opcode.SWAP -> InsSWAP(ins)
+ Opcode.COPY -> InsCOPY(ins)
+ Opcode.COPYZ -> InsCOPYZ(ins)
+ Opcode.BREAKPOINT -> InsBREAKPOINT()
+ else -> throw IllegalArgumentException("invalid opcode ${ins.opcode}")
+ }
+ }
+
+ private fun InsSYSCALL(ins: Instruction) {
+ val call = Syscall.values()[ins.value!!]
+ SysCalls.call(call, this)
+ pc++
+ }
+
+ private fun InsBREAKPOINT() {
+ pc++
+ throw BreakpointException(pc)
+ }
+
+ private fun InsLOAD(i: Instruction) {
+ when(i.type!!) {
+ DataType.BYTE -> registers.setB(i.reg1!!, i.value!!.toUByte())
+ DataType.WORD -> registers.setW(i.reg1!!, i.value!!.toUShort())
+ }
+ pc++
+ }
+
+ private fun InsLOADM(i: Instruction) {
+ when(i.type!!) {
+ DataType.BYTE -> registers.setB(i.reg1!!, memory.getB(i.value!!))
+ DataType.WORD -> registers.setW(i.reg1!!, memory.getW(i.value!!))
+ }
+ pc++
+ }
+
+ private fun InsLOADI(i: Instruction) {
+ when(i.type!!) {
+ DataType.BYTE -> registers.setB(i.reg1!!, memory.getB(registers.getW(i.reg2!!).toInt()))
+ DataType.WORD -> registers.setW(i.reg1!!, memory.getW(registers.getW(i.reg2!!).toInt()))
+ }
+ pc++
+ }
+ private fun InsLOADX(i: Instruction) {
+ when (i.type!!) {
+ DataType.BYTE -> registers.setB(i.reg1!!, memory.getB(i.value!! + registers.getW(i.reg2!!).toInt()))
+ DataType.WORD -> registers.setW(i.reg1!!, memory.getW(i.value!! + registers.getW(i.reg2!!).toInt()))
+ }
+ pc++
+ }
+
+ private fun InsLOADR(i: Instruction) {
+ when(i.type!!) {
+ DataType.BYTE -> registers.setB(i.reg1!!, registers.getB(i.reg2!!))
+ DataType.WORD -> registers.setW(i.reg1!!, registers.getW(i.reg2!!))
+ }
+ pc++
+ }
+
+ private fun InsSWAPREG(i: Instruction) {
+ when(i.type!!) {
+ DataType.BYTE -> {
+ val oldR2 = registers.getB(i.reg2!!)
+ registers.setB(i.reg2, registers.getB(i.reg1!!))
+ registers.setB(i.reg1, oldR2)
+ }
+ DataType.WORD -> {
+ val oldR2 = registers.getW(i.reg2!!)
+ registers.setW(i.reg2, registers.getW(i.reg1!!))
+ registers.setW(i.reg1, oldR2)
+ }
+ }
+ pc++
+ }
+
+ private fun InsSTOREM(i: Instruction) {
+ when(i.type!!) {
+ DataType.BYTE -> memory.setB(i.value!!, registers.getB(i.reg1!!))
+ DataType.WORD -> memory.setW(i.value!!, registers.getW(i.reg1!!))
+ }
+ pc++
+ }
+
+ private fun InsSTOREI(i: Instruction) {
+ when (i.type!!) {
+ DataType.BYTE -> memory.setB(registers.getW(i.reg2!!).toInt(), registers.getB(i.reg1!!))
+ DataType.WORD -> memory.setW(registers.getW(i.reg2!!).toInt(), registers.getW(i.reg1!!))
+ }
+ pc++
+ }
+
+ private fun InsSTOREX(i: Instruction) {
+ when (i.type!!) {
+ DataType.BYTE -> memory.setB(registers.getW(i.reg2!!).toInt() + i.value!!, registers.getB(i.reg1!!))
+ DataType.WORD -> memory.setW(registers.getW(i.reg2!!).toInt() + i.value!!, registers.getW(i.reg1!!))
+ }
+ pc++
+ }
+
+ private fun InsSTOREZ(i: Instruction) {
+ when(i.type!!) {
+ DataType.BYTE -> memory.setB(i.value!!, 0u)
+ DataType.WORD -> memory.setW(i.value!!, 0u)
+ }
+ }
+
+ private fun InsSTOREZI(i: Instruction) {
+ when (i.type!!) {
+ DataType.BYTE -> memory.setB(registers.getW(i.reg2!!).toInt(), 0u)
+ DataType.WORD -> memory.setW(registers.getW(i.reg2!!).toInt(), 0u)
+ }
+ pc++
+ }
+ private fun InsSTOREZX(i: Instruction) {
+ when (i.type!!) {
+ DataType.BYTE -> memory.setB(registers.getW(i.reg2!!).toInt() + i.value!!, 0u)
+ DataType.WORD -> memory.setW(registers.getW(i.reg2!!).toInt() + i.value!!, 0u)
+ }
+ pc++
+ }
+
+ private fun InsJUMP(i: Instruction) {
+ pc = i.value!!
+ }
+
+ private fun InsJUMPI(i: Instruction) {
+ pc = registers.getW(i.reg1!!).toInt()
+ }
+
+ private fun InsGOSUB(i: Instruction) {
+ callStack.push(pc+1)
+ pc = i.value!!
+ }
+
+ private fun InsGOSUBI(i: Instruction) {
+ callStack.push(pc+1)
+ pc = registers.getW(i.reg1!!).toInt()
+ }
+
+ private fun InsRETURN() {
+ if(callStack.isEmpty())
+ exit()
+ else
+ pc = callStack.pop()
+ }
+
+ private fun InsBZ(i: Instruction) {
+ when(i.type!!) {
+ DataType.BYTE -> {
+ if(registers.getB(i.reg1!!)==0.toUByte())
+ pc = i.value!!
+ else
+ pc++
+ }
+ DataType.WORD -> {
+ if(registers.getW(i.reg1!!)==0.toUShort())
+ pc = i.value!!
+ else
+ pc++
+ }
+ }
+ }
+
+ private fun InsBNZ(i: Instruction) {
+ when(i.type!!) {
+ DataType.BYTE -> {
+ if(registers.getB(i.reg1!!)!=0.toUByte())
+ pc = i.value!!
+ else
+ pc++
+ }
+ DataType.WORD -> {
+ if(registers.getW(i.reg1!!)!=0.toUShort())
+ pc = i.value!!
+ else
+ pc++
+ }
+ }
+ }
+
+ private fun InsBEQ(i: Instruction) {
+ val (left: Int, right: Int) = getBranchOperands(i)
+ if(left==right)
+ pc = i.value!!
+ else
+ pc++
+ }
+
+ private fun getBranchOperands(i: Instruction): Pair {
+ return when(i.type) {
+ DataType.BYTE -> Pair(registers.getB(i.reg1!!).toInt(), registers.getB(i.reg2!!).toInt())
+ DataType.WORD -> Pair(registers.getW(i.reg1!!).toInt(), registers.getW(i.reg2!!).toInt())
+ null -> throw IllegalArgumentException("need type for branch instruction")
+ }
+ }
+
+ private fun getBranchOperandsU(i: Instruction): Pair {
+ return when(i.type) {
+ DataType.BYTE -> Pair(registers.getB(i.reg1!!).toUInt(), registers.getB(i.reg2!!).toUInt())
+ DataType.WORD -> Pair(registers.getW(i.reg1!!).toUInt(), registers.getW(i.reg2!!).toUInt())
+ null -> throw IllegalArgumentException("need type for branch instruction")
+ }
+ }
+
+ private fun getLogicalOperandsU(i: Instruction): Pair {
+ return when(i.type) {
+ DataType.BYTE -> Pair(registers.getB(i.reg2!!).toUInt(), registers.getB(i.reg3!!).toUInt())
+ DataType.WORD -> Pair(registers.getW(i.reg2!!).toUInt(), registers.getW(i.reg3!!).toUInt())
+ null -> throw IllegalArgumentException("need type for logical instruction")
+ }
+ }
+
+ private fun InsBNE(i: Instruction) {
+ val (left: Int, right: Int) = getBranchOperands(i)
+ if(left!=right)
+ pc = i.value!!
+ else
+ pc++
+ }
+
+ private fun InsBLTU(i: Instruction) {
+ val (left: UInt, right: UInt) = getBranchOperandsU(i)
+ if(leftright)
+ pc = i.value!!
+ else
+ pc++
+
+ }
+
+ private fun InsBGTS(i: Instruction) {
+ val (left: Int, right: Int) = getBranchOperands(i)
+ if(left>right)
+ pc = i.value!!
+ else
+ pc++
+ }
+
+ private fun InsBLEU(i: Instruction) {
+ val (left: UInt, right: UInt) = getBranchOperandsU(i)
+ if(left<=right)
+ pc = i.value!!
+ else
+ pc++
+
+ }
+
+ private fun InsBLES(i: Instruction) {
+ val (left: Int, right: Int) = getBranchOperands(i)
+ if(left<=right)
+ pc = i.value!!
+ else
+ pc++
+ }
+
+ private fun InsBGEU(i: Instruction) {
+ val (left: UInt, right: UInt) = getBranchOperandsU(i)
+ if(left>=right)
+ pc = i.value!!
+ else
+ pc++
+
+ }
+
+ private fun InsBGES(i: Instruction) {
+ val (left: Int, right: Int) = getBranchOperands(i)
+ if(left>=right)
+ pc = i.value!!
+ else
+ pc++
+ }
+
+ private fun InsNEG(i: Instruction) {
+ when(i.type!!) {
+ DataType.BYTE -> registers.setB(i.reg1!!, (-registers.getB(i.reg2!!).toInt()).toUByte())
+ DataType.WORD -> registers.setW(i.reg1!!, (-registers.getW(i.reg2!!).toInt()).toUShort())
+ }
+ pc++
+ }
+
+ private fun InsADD(i: Instruction) {
+ when(i.type!!) {
+ DataType.BYTE -> arithByte("+", i.reg1!!, i.reg2!!, i.reg3!!, null)
+ DataType.WORD -> arithWord("+", i.reg1!!, i.reg2!!, i.reg3!!, null)
+ }
+ pc++
+ }
+
+ private fun InsMul(i: Instruction) {
+ when(i.type!!) {
+ DataType.BYTE -> arithByte("*", i.reg1!!, i.reg2!!, i.reg3!!, null)
+ DataType.WORD -> arithWord("*", i.reg1!!, i.reg2!!, i.reg3!!, null)
+ }
+ pc++
+ }
+
+ private fun InsDiv(i: Instruction) {
+ when(i.type!!) {
+ DataType.BYTE -> arithByte("/", i.reg1!!, i.reg2!!, i.reg3!!, null)
+ DataType.WORD -> arithWord("/", i.reg1!!, i.reg2!!, i.reg3!!, null)
+ }
+ pc++
+ }
+
+ private fun arithByte(operator: String, reg1: Int, reg2: Int, reg3: Int?, value: UByte?) {
+ val left = registers.getB(reg2)
+ val right = value ?: registers.getB(reg3!!)
+ val result = when(operator) {
+ "+" -> left + right
+ "-" -> left - right
+ "*" -> left * right
+ "/" -> {
+ if(right==0.toUByte()) 0xffu
+ else left / right
+ }
+ else -> TODO("operator $operator")
+ }
+ registers.setB(reg1, result.toUByte())
+ }
+
+ private fun arithWord(operator: String, reg1: Int, reg2: Int, reg3: Int?, value: UShort?) {
+ val left = registers.getW(reg2)
+ val right = value ?: registers.getW(reg3!!)
+ val result = when(operator) {
+ "+" -> left + right
+ "-" -> left - right
+ "*" -> left * right
+ "/" -> {
+ if(right==0.toUShort()) 0xffffu
+ else left / right
+ }
+ else -> TODO("operator $operator")
+ }
+ registers.setW(reg1, result.toUShort())
+ }
+
+ private fun InsADDI(i: Instruction) {
+ when(i.type!!) {
+ DataType.BYTE -> arithByte("+", i.reg1!!, i.reg2!!, null, i.value!!.toUByte())
+ DataType.WORD -> arithWord("+", i.reg1!!, i.reg2!!, null, i.value!!.toUShort())
+ }
+ pc++
+ }
+
+ private fun InsSUB(i: Instruction) {
+ when(i.type!!) {
+ DataType.BYTE -> arithByte("-", i.reg1!!, i.reg2!!, i.reg3!!, null)
+ DataType.WORD -> arithWord("-", i.reg1!!, i.reg2!!, i.reg3!!, null)
+ }
+ pc++
+ }
+
+ private fun InsSUBI(i: Instruction) {
+ when(i.type!!) {
+ DataType.BYTE -> arithByte("-", i.reg1!!, i.reg2!!, null, i.value!!.toUByte())
+ DataType.WORD -> arithWord("-", i.reg1!!, i.reg2!!, null, i.value!!.toUShort())
+ }
+ pc++
+ }
+
+ private fun InsEXT(i: Instruction) {
+ when(i.type!!){
+ DataType.BYTE -> registers.setW(i.reg1!!, registers.getB(i.reg2!!).toUShort())
+ DataType.WORD -> TODO("ext.w requires 32 bits registers")
+ }
+ pc++
+ }
+
+ private fun InsEXTS(i: Instruction) {
+ when(i.type!!){
+ DataType.BYTE -> registers.setW(i.reg1!!, (registers.getB(i.reg2!!).toByte()).toUShort())
+ DataType.WORD -> TODO("ext.w requires 32 bits registers")
+ }
+ pc++
+ }
+
+ private fun InsINV(i: Instruction) {
+ when(i.type!!) {
+ DataType.BYTE -> registers.setB(i.reg1!!, registers.getB(i.reg2!!).inv())
+ DataType.WORD -> registers.setW(i.reg1!!, registers.getW(i.reg2!!).inv())
+ }
+ pc++
+ }
+
+ private fun InsAND(i: Instruction) {
+ val (left: UInt, right: UInt) = getLogicalOperandsU(i)
+ when(i.type!!) {
+ DataType.BYTE -> registers.setB(i.reg1!!, (left and right).toUByte())
+ DataType.WORD -> registers.setW(i.reg1!!, (left and right).toUShort())
+ }
+ pc++
+ }
+
+ private fun InsOR(i: Instruction) {
+ val (left: UInt, right: UInt) = getLogicalOperandsU(i)
+ when(i.type!!) {
+ DataType.BYTE -> registers.setB(i.reg1!!, (left or right).toUByte())
+ DataType.WORD -> registers.setW(i.reg1!!, (left or right).toUShort())
+ }
+ pc++
+ }
+
+ private fun InsXOR(i: Instruction) {
+ val (left: UInt, right: UInt) = getLogicalOperandsU(i)
+ when(i.type!!) {
+ DataType.BYTE -> registers.setB(i.reg1!!, (left xor right).toUByte())
+ DataType.WORD -> registers.setW(i.reg1!!, (left xor right).toUShort())
+ }
+ pc++
+ }
+
+ private fun InsLSR(i: Instruction) {
+ when(i.type!!) {
+ DataType.BYTE -> registers.setB(i.reg1!!, (registers.getB(i.reg2!!).toInt() ushr 1).toUByte())
+ DataType.WORD -> registers.setW(i.reg1!!, (registers.getW(i.reg2!!).toInt() ushr 1).toUShort())
+ }
+ pc++
+ }
+
+ private fun InsLSL(i: Instruction) {
+ when(i.type!!) {
+ DataType.BYTE -> registers.setB(i.reg1!!, (registers.getB(i.reg2!!).toInt() shl 1).toUByte())
+ DataType.WORD -> registers.setW(i.reg1!!, (registers.getW(i.reg2!!).toInt() shl 1).toUShort())
+ }
+ pc++
+ }
+
+ private fun InsROR(i: Instruction) {
+ when(i.type!!) {
+ DataType.BYTE -> registers.setB(i.reg1!!, (registers.getB(i.reg2!!).toInt().rotateRight(1).toUByte()))
+ DataType.WORD -> registers.setW(i.reg1!!, (registers.getW(i.reg2!!).toInt().rotateRight(1).toUShort()))
+ }
+ pc++
+ }
+
+ private fun InsROL(i: Instruction) {
+ when(i.type!!) {
+ DataType.BYTE -> registers.setB(i.reg1!!, (registers.getB(i.reg2!!).toInt().rotateLeft(1).toUByte()))
+ DataType.WORD -> registers.setW(i.reg1!!, (registers.getW(i.reg2!!).toInt().rotateLeft(1).toUShort()))
+ }
+ pc++
+ }
+
+ private fun InsCOPY(i: Instruction) = doCopy(i.reg1!!, i.reg2!!, i.reg3!!, false)
+
+ private fun InsCOPYZ(i: Instruction) = doCopy(i.reg1!!, i.reg2!!, null, true)
+
+ private fun doCopy(reg1: Int, reg2: Int, length: Int?, untilzero: Boolean) {
+ var from = registers.getW(reg1).toInt()
+ var to = registers.getW(reg2).toInt()
+ if(untilzero) {
+ while(true) {
+ val char = memory.getB(from).toInt()
+ memory.setB(to, char.toUByte())
+ if(char==0)
+ break
+ from++
+ to++
+ }
+ } else {
+ var len = length!!
+ while(len>0) {
+ val char = memory.getB(from).toInt()
+ memory.setB(to, char.toUByte())
+ from++
+ to++
+ len--
+ }
+ }
+ pc++
+ }
+
+ private fun InsSWAP(i: Instruction) {
+ when(i.type!!) {
+ DataType.BYTE -> {
+ val value = registers.getW(i.reg2!!)
+ val newValue = value.toUByte()*256u + (value.toInt() ushr 8).toUInt()
+ registers.setW(i.reg1!!, newValue.toUShort())
+ }
+ DataType.WORD -> TODO("swap.w requires 32-bits registers")
+ }
+ pc++
+ }
+
+ private var window: GraphicsWindow? = null
+
+ fun gfx_enable() {
+ window = when(registers.getB(0).toInt()) {
+ 0 -> GraphicsWindow(320, 240, 3)
+ 1 -> GraphicsWindow(640, 480, 2)
+ else -> throw IllegalArgumentException("invalid screen mode")
+ }
+ window!!.start()
+ }
+
+ fun gfx_clear() {
+ window?.clear(registers.getB(0).toInt())
+ }
+
+ fun gfx_plot() {
+ window?.plot(registers.getW(0).toInt(), registers.getW(1).toInt(), registers.getB(2).toInt())
+ }
+
+ fun gfx_close() {
+ window?.close()
+ }
+}
diff --git a/virtualmachine/virtualmachine.iml b/virtualmachine/virtualmachine.iml
new file mode 100644
index 000000000..132600e11
--- /dev/null
+++ b/virtualmachine/virtualmachine.iml
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file