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