added start of virtual machine compilation target

This commit is contained in:
Irmen de Jong 2022-03-19 00:57:35 +01:00
parent a99e77093f
commit 7d2bf892b1
35 changed files with 1971 additions and 32 deletions

View File

@ -6,6 +6,7 @@
<module fileurl="file://$PROJECT_DIR$/codeCore/codeCore.iml" filepath="$PROJECT_DIR$/codeCore/codeCore.iml" />
<module fileurl="file://$PROJECT_DIR$/codeGenCpu6502/codeGenCpu6502.iml" filepath="$PROJECT_DIR$/codeGenCpu6502/codeGenCpu6502.iml" />
<module fileurl="file://$PROJECT_DIR$/codeGenExperimental/codeGenExperimental.iml" filepath="$PROJECT_DIR$/codeGenExperimental/codeGenExperimental.iml" />
<module fileurl="file://$PROJECT_DIR$/codeGenVirtual/codeGenVirtual.iml" filepath="$PROJECT_DIR$/codeGenVirtual/codeGenVirtual.iml" />
<module fileurl="file://$PROJECT_DIR$/codeOptimizers/codeOptimizers.iml" filepath="$PROJECT_DIR$/codeOptimizers/codeOptimizers.iml" />
<module fileurl="file://$PROJECT_DIR$/compiler/compiler.iml" filepath="$PROJECT_DIR$/compiler/compiler.iml" />
<module fileurl="file://$PROJECT_DIR$/compilerAst/compilerAst.iml" filepath="$PROJECT_DIR$/compilerAst/compilerAst.iml" />
@ -14,6 +15,7 @@
<module fileurl="file://$PROJECT_DIR$/examples/examples.iml" filepath="$PROJECT_DIR$/examples/examples.iml" />
<module fileurl="file://$PROJECT_DIR$/httpCompilerService/httpCompilerService.iml" filepath="$PROJECT_DIR$/httpCompilerService/httpCompilerService.iml" />
<module fileurl="file://$PROJECT_DIR$/parser/parser.iml" filepath="$PROJECT_DIR$/parser/parser.iml" />
<module fileurl="file://$PROJECT_DIR$/virtualmachine/virtualmachine.iml" filepath="$PROJECT_DIR$/virtualmachine/virtualmachine.iml" />
</modules>
</component>
</project>

View File

@ -4,6 +4,7 @@
<exclude-output />
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
<excludeFolder url="file://$MODULE_DIR$/build" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />

View File

@ -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"
}

View File

@ -4,10 +4,12 @@
<exclude-output />
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
<excludeFolder url="file://$MODULE_DIR$/build" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="library" name="KotlinJavaRuntime" level="project" />
<orderEntry type="library" name="michael.bull.kotlin.result.jvm" level="project" />
<orderEntry type="module" module-name="virtualmachine" />
</component>
</module>

View File

@ -10,7 +10,8 @@ interface IMachineFloat {
enum class CpuType {
CPU6502,
CPU65c02
CPU65c02,
VIRTUAL
}
interface IMachineDefinition {

View File

@ -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
}
}

View File

@ -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
}
}
}

View File

@ -23,7 +23,7 @@ class AtariMachineDefinition: IMachineDefinition {
override fun getFloat(num: Number) = TODO("float from number")
override fun importLibs(compilerOptions: CompilationOptions, compilationTargetName: String): List<String> {
return if (compilerOptions.launcher == CbmPrgLauncherType.BASIC || compilerOptions.output == OutputType.PRG)
return if (compilerOptions.output == OutputType.XEX)
listOf("syslib")
else
emptyList()

View File

@ -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<String> {
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<String>()
}

View File

@ -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)

View File

@ -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? {

View File

@ -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!

View File

@ -0,0 +1,16 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="JAVA_MODULE" version="4">
<component name="NewModuleRootManager" inherit-compiler-output="true">
<exclude-output />
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="library" name="KotlinJavaRuntime" level="project" />
<orderEntry type="library" name="michael.bull.kotlin.result.jvm" level="project" />
<orderEntry type="module" module-name="codeAst" />
<orderEntry type="module" module-name="codeCore" />
<orderEntry type="module" module-name="virtualmachine" />
</component>
</module>

View File

@ -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"""
}

View File

@ -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)
}
}

View File

@ -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"

View File

@ -22,5 +22,6 @@
<orderEntry type="module" module-name="codeOptimizers" />
<orderEntry type="module" module-name="codeGenCpu6502" />
<orderEntry type="module" module-name="codeGenExperimental" />
<orderEntry type="module" module-name="codeGenVirtual" />
</component>
</module>

View File

@ -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
}

View File

@ -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
}
}

View File

@ -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<String>): 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<String>): 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
}

View File

@ -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}")

View File

@ -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" {

View File

@ -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()
}

View File

@ -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

View File

@ -4,6 +4,8 @@ include(
':codeAst',
':compilerAst',
':codeOptimizers',
':virtualmachine',
':codeGenVirtual',
':codeGenCpu6502',
':codeGenExperimental',
':compiler',

View File

@ -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!

View File

@ -0,0 +1,214 @@
package prog8.vm
class Assembler {
private val labels = mutableMapOf<String, Int>()
private val placeholders = mutableMapOf<Int, String>()
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<Instruction> {
labels.clear()
placeholders.clear()
val program = mutableListOf<Instruction>()
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<Instruction>) {
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<Char>()
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("")
}
}

View File

@ -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()
}
}

View File

@ -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<DataType>, val reg1: Boolean, val reg2: Boolean, val reg3: Boolean, val value: Boolean)
private val NN = emptySet<DataType>()
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)
)

View File

@ -0,0 +1,43 @@
package prog8.vm
fun main(args: Array<String>) {
val memsrc = """
$4000 strz "Hello from program! "derp" bye.\n"
$2000 ubyte 65,66,67,68,0
$2100 uword $1111,$2222,$3333,$4444
"""
val src = """
; enable lores gfx screen
load r0, 0
syscall 8
load.w r10, 320
load.w r11, 240
load.b r12, 0
_forever:
load.w r1, 0
_yloop:
load.w r0, 0
_xloop:
mul.b r2,r0,r1
add.b r2,r2,r12
syscall 10
addi.w r0,r0,1
blt.w r0, r10, _xloop
addi.w r1,r1,1
blt.w r1, r11,_yloop
addi.b r12,r12,1
jump _forever
load.w r0, 2000
syscall 7
load.w r0,0
return"""
val memory = Memory()
val assembler = Assembler()
assembler.initializeMemory(memsrc, memory)
val program = assembler.assembleProgram(src)
val vm = VirtualMachine(memory, program)
vm.run()
}

View File

@ -0,0 +1,65 @@
package prog8.vm
/**
* 64 Kb of random access memory.
*/
class Memory {
private val mem = Array<UByte>(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()
}
}

View File

@ -0,0 +1,24 @@
package prog8.vm
/**
* 65536 virtual registers of 16 bits wide.
*/
class Registers {
private val registers = Array<UShort>(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]
}

View File

@ -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()
}
}
}

View File

@ -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<Instruction>) {
val registers = Registers()
val program: Array<Instruction> = program.toTypedArray()
val callStack = Stack<Int>()
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<Int, Int> {
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<UInt, UInt> {
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<UInt, UInt> {
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(left<right)
pc = i.value!!
else
pc++
}
private fun InsBLTS(i: Instruction) {
val (left: Int, right: Int) = getBranchOperands(i)
if(left<right)
pc = i.value!!
else
pc++
}
private fun InsBGTU(i: Instruction) {
val (left: UInt, right: UInt) = getBranchOperandsU(i)
if(left>right)
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()
}
}

View File

@ -0,0 +1,13 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="JAVA_MODULE" version="4">
<component name="NewModuleRootManager" inherit-compiler-output="true">
<exclude-output />
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
<excludeFolder url="file://$MODULE_DIR$/build" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="library" name="KotlinJavaRuntime" level="project" />
</component>
</module>