mirror of
https://github.com/irmen/prog8.git
synced 2025-11-03 19:16:13 +00:00
added start of virtual machine compilation target
This commit is contained in:
2
.idea/modules.xml
generated
2
.idea/modules.xml
generated
@@ -6,6 +6,7 @@
|
|||||||
<module fileurl="file://$PROJECT_DIR$/codeCore/codeCore.iml" filepath="$PROJECT_DIR$/codeCore/codeCore.iml" />
|
<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$/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$/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$/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$/compiler/compiler.iml" filepath="$PROJECT_DIR$/compiler/compiler.iml" />
|
||||||
<module fileurl="file://$PROJECT_DIR$/compilerAst/compilerAst.iml" filepath="$PROJECT_DIR$/compilerAst/compilerAst.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$/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$/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$/parser/parser.iml" filepath="$PROJECT_DIR$/parser/parser.iml" />
|
||||||
|
<module fileurl="file://$PROJECT_DIR$/virtualmachine/virtualmachine.iml" filepath="$PROJECT_DIR$/virtualmachine/virtualmachine.iml" />
|
||||||
</modules>
|
</modules>
|
||||||
</component>
|
</component>
|
||||||
</project>
|
</project>
|
||||||
@@ -4,6 +4,7 @@
|
|||||||
<exclude-output />
|
<exclude-output />
|
||||||
<content url="file://$MODULE_DIR$">
|
<content url="file://$MODULE_DIR$">
|
||||||
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
|
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
|
||||||
|
<excludeFolder url="file://$MODULE_DIR$/build" />
|
||||||
</content>
|
</content>
|
||||||
<orderEntry type="inheritedJdk" />
|
<orderEntry type="inheritedJdk" />
|
||||||
<orderEntry type="sourceFolder" forTests="false" />
|
<orderEntry type="sourceFolder" forTests="false" />
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ compileTestKotlin {
|
|||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
|
implementation project(':virtualmachine')
|
||||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8"
|
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8"
|
||||||
implementation "com.michael-bull.kotlin-result:kotlin-result-jvm:1.1.14"
|
implementation "com.michael-bull.kotlin-result:kotlin-result-jvm:1.1.14"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,10 +4,12 @@
|
|||||||
<exclude-output />
|
<exclude-output />
|
||||||
<content url="file://$MODULE_DIR$">
|
<content url="file://$MODULE_DIR$">
|
||||||
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
|
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
|
||||||
|
<excludeFolder url="file://$MODULE_DIR$/build" />
|
||||||
</content>
|
</content>
|
||||||
<orderEntry type="inheritedJdk" />
|
<orderEntry type="inheritedJdk" />
|
||||||
<orderEntry type="sourceFolder" forTests="false" />
|
<orderEntry type="sourceFolder" forTests="false" />
|
||||||
<orderEntry type="library" name="KotlinJavaRuntime" level="project" />
|
<orderEntry type="library" name="KotlinJavaRuntime" level="project" />
|
||||||
<orderEntry type="library" name="michael.bull.kotlin.result.jvm" level="project" />
|
<orderEntry type="library" name="michael.bull.kotlin.result.jvm" level="project" />
|
||||||
|
<orderEntry type="module" module-name="virtualmachine" />
|
||||||
</component>
|
</component>
|
||||||
</module>
|
</module>
|
||||||
@@ -10,7 +10,8 @@ interface IMachineFloat {
|
|||||||
|
|
||||||
enum class CpuType {
|
enum class CpuType {
|
||||||
CPU6502,
|
CPU6502,
|
||||||
CPU65c02
|
CPU65c02,
|
||||||
|
VIRTUAL
|
||||||
}
|
}
|
||||||
|
|
||||||
interface IMachineDefinition {
|
interface IMachineDefinition {
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ class AtariTarget: ICompilationTarget, IStringEncoding by Encoder, IMemSizer {
|
|||||||
return when(dt) {
|
return when(dt) {
|
||||||
in ByteDatatypes -> 1
|
in ByteDatatypes -> 1
|
||||||
in WordDatatypes, in PassByReferenceDatatypes -> 2
|
in WordDatatypes, in PassByReferenceDatatypes -> 2
|
||||||
DataType.FLOAT -> 6
|
DataType.FLOAT -> machine.FLOAT_MEM_SIZE
|
||||||
else -> Int.MIN_VALUE
|
else -> Int.MIN_VALUE
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
24
codeCore/src/prog8/code/target/VMTarget.kt
Normal file
24
codeCore/src/prog8/code/target/VMTarget.kt
Normal 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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -23,7 +23,7 @@ class AtariMachineDefinition: IMachineDefinition {
|
|||||||
override fun getFloat(num: Number) = TODO("float from number")
|
override fun getFloat(num: Number) = TODO("float from number")
|
||||||
|
|
||||||
override fun importLibs(compilerOptions: CompilationOptions, compilationTargetName: String): List<String> {
|
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")
|
listOf("syslib")
|
||||||
else
|
else
|
||||||
emptyList()
|
emptyList()
|
||||||
|
|||||||
@@ -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>()
|
||||||
|
}
|
||||||
@@ -22,9 +22,10 @@ internal const val subroutineFloatEvalResultVar2 = "prog8_float_eval_result2"
|
|||||||
|
|
||||||
|
|
||||||
class AsmGen(internal val program: Program,
|
class AsmGen(internal val program: Program,
|
||||||
internal val errors: IErrorReporter,
|
|
||||||
internal val symbolTable: SymbolTable,
|
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 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)
|
internal val optimizedWordMultiplications = setOf(3,5,6,7,9,10,12,15,20,25,40,50,80,100,320,640)
|
||||||
|
|||||||
@@ -19,9 +19,9 @@ import prog8.code.core.IErrorReporter
|
|||||||
|
|
||||||
|
|
||||||
class AsmGen(internal val program: PtProgram,
|
class AsmGen(internal val program: PtProgram,
|
||||||
internal val errors: IErrorReporter,
|
|
||||||
internal val symbolTable: SymbolTable,
|
internal val symbolTable: SymbolTable,
|
||||||
internal val options: CompilationOptions
|
internal val options: CompilationOptions,
|
||||||
|
internal val errors: IErrorReporter
|
||||||
): IAssemblyGenerator {
|
): IAssemblyGenerator {
|
||||||
|
|
||||||
override fun compileToAssembly(): IAssemblyProgram? {
|
override fun compileToAssembly(): IAssemblyProgram? {
|
||||||
|
|||||||
47
codeGenVirtual/build.gradle
Normal file
47
codeGenVirtual/build.gradle
Normal 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!
|
||||||
16
codeGenVirtual/codeGenVirtual.iml
Normal file
16
codeGenVirtual/codeGenVirtual.iml
Normal 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>
|
||||||
55
codeGenVirtual/src/prog8/codegen/virtual/AssemblyProgram.kt
Normal file
55
codeGenVirtual/src/prog8/codegen/virtual/AssemblyProgram.kt
Normal 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"""
|
||||||
|
|
||||||
|
}
|
||||||
20
codeGenVirtual/src/prog8/codegen/virtual/CodeGen.kt
Normal file
20
codeGenVirtual/src/prog8/codegen/virtual/CodeGen.kt
Normal 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -32,6 +32,7 @@ dependencies {
|
|||||||
implementation project(':codeOptimizers')
|
implementation project(':codeOptimizers')
|
||||||
implementation project(':compilerAst')
|
implementation project(':compilerAst')
|
||||||
implementation project(':codeGenCpu6502')
|
implementation project(':codeGenCpu6502')
|
||||||
|
implementation project(':codeGenVirtual')
|
||||||
implementation project(':codeGenExperimental')
|
implementation project(':codeGenExperimental')
|
||||||
implementation 'org.antlr:antlr4-runtime:4.9.3'
|
implementation 'org.antlr:antlr4-runtime:4.9.3'
|
||||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8"
|
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8"
|
||||||
|
|||||||
@@ -22,5 +22,6 @@
|
|||||||
<orderEntry type="module" module-name="codeOptimizers" />
|
<orderEntry type="module" module-name="codeOptimizers" />
|
||||||
<orderEntry type="module" module-name="codeGenCpu6502" />
|
<orderEntry type="module" module-name="codeGenCpu6502" />
|
||||||
<orderEntry type="module" module-name="codeGenExperimental" />
|
<orderEntry type="module" module-name="codeGenExperimental" />
|
||||||
|
<orderEntry type="module" module-name="codeGenVirtual" />
|
||||||
</component>
|
</component>
|
||||||
</module>
|
</module>
|
||||||
151
compiler/res/prog8lib/virtual/syslib.p8
Normal file
151
compiler/res/prog8lib/virtual/syslib.p8
Normal 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
|
||||||
|
}
|
||||||
107
compiler/res/prog8lib/virtual/textio.p8
Normal file
107
compiler/res/prog8lib/virtual/textio.p8
Normal 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
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -3,10 +3,7 @@ package prog8
|
|||||||
import kotlinx.cli.*
|
import kotlinx.cli.*
|
||||||
import prog8.ast.base.AstException
|
import prog8.ast.base.AstException
|
||||||
import prog8.code.core.CbmPrgLauncherType
|
import prog8.code.core.CbmPrgLauncherType
|
||||||
import prog8.code.target.AtariTarget
|
import prog8.code.target.*
|
||||||
import prog8.code.target.C128Target
|
|
||||||
import prog8.code.target.C64Target
|
|
||||||
import prog8.code.target.Cx16Target
|
|
||||||
import prog8.compiler.CompilationResult
|
import prog8.compiler.CompilationResult
|
||||||
import prog8.compiler.CompilerArguments
|
import prog8.compiler.CompilerArguments
|
||||||
import prog8.compiler.compileProgram
|
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 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 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 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(
|
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}')")
|
||||||
C64Target.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 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)
|
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()!=".")
|
if(srcdirs.firstOrNull()!=".")
|
||||||
srcdirs.add(0, ".")
|
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")
|
System.err.println("Invalid compilation target: $compilationTarget")
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,17 +13,13 @@ import prog8.ast.statements.VarDecl
|
|||||||
import prog8.ast.walk.IAstVisitor
|
import prog8.ast.walk.IAstVisitor
|
||||||
import prog8.code.SymbolTable
|
import prog8.code.SymbolTable
|
||||||
import prog8.code.core.*
|
import prog8.code.core.*
|
||||||
import prog8.code.target.AtariTarget
|
import prog8.code.target.*
|
||||||
import prog8.code.target.C128Target
|
|
||||||
import prog8.code.target.C64Target
|
|
||||||
import prog8.code.target.Cx16Target
|
|
||||||
import prog8.compiler.astprocessing.*
|
import prog8.compiler.astprocessing.*
|
||||||
import prog8.optimizer.*
|
import prog8.optimizer.*
|
||||||
import prog8.parser.ParseError
|
import prog8.parser.ParseError
|
||||||
import java.nio.file.Path
|
import java.nio.file.Path
|
||||||
import kotlin.io.path.Path
|
import kotlin.io.path.Path
|
||||||
import kotlin.io.path.nameWithoutExtension
|
import kotlin.io.path.nameWithoutExtension
|
||||||
import kotlin.math.exp
|
|
||||||
import kotlin.math.round
|
import kotlin.math.round
|
||||||
import kotlin.system.measureTimeMillis
|
import kotlin.system.measureTimeMillis
|
||||||
|
|
||||||
@@ -59,6 +55,7 @@ fun compileProgram(args: CompilerArguments): CompilationResult? {
|
|||||||
C128Target.NAME -> C128Target()
|
C128Target.NAME -> C128Target()
|
||||||
Cx16Target.NAME -> Cx16Target()
|
Cx16Target.NAME -> Cx16Target()
|
||||||
AtariTarget.NAME -> AtariTarget()
|
AtariTarget.NAME -> AtariTarget()
|
||||||
|
VMTarget.NAME -> VMTarget()
|
||||||
else -> throw IllegalArgumentException("invalid compilation target")
|
else -> throw IllegalArgumentException("invalid compilation target")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -444,13 +441,18 @@ internal fun asmGeneratorFor(program: Program,
|
|||||||
if(options.experimentalCodegen) {
|
if(options.experimentalCodegen) {
|
||||||
if (options.compTarget.machine.cpu in arrayOf(CpuType.CPU6502, CpuType.CPU65c02)) {
|
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()
|
val intermediateAst = IntermediateAstMaker(program).transform()
|
||||||
return prog8.codegen.experimental.AsmGen(intermediateAst, errors, symbolTable, options)
|
return prog8.codegen.experimental.AsmGen(intermediateAst, symbolTable, options, errors)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (options.compTarget.machine.cpu in arrayOf(CpuType.CPU6502, CpuType.CPU65c02))
|
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}")
|
throw NotImplementedError("no asm generator for cpu ${options.compTarget.machine.cpu}")
|
||||||
|
|||||||
@@ -75,7 +75,7 @@ class TestAsmGenSymbols: StringSpec({
|
|||||||
val options = CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.FULL, emptyList(), false, true, C64Target(), 999u)
|
val options = CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.FULL, emptyList(), false, true, C64Target(), 999u)
|
||||||
options.compTarget.machine.zeropage = C64Zeropage(options)
|
options.compTarget.machine.zeropage = C64Zeropage(options)
|
||||||
val st = SymbolTableMaker().makeFrom(program)
|
val st = SymbolTableMaker().makeFrom(program)
|
||||||
return AsmGen(program, errors, st, options)
|
return AsmGen(program, st, options, errors)
|
||||||
}
|
}
|
||||||
|
|
||||||
"symbol and variable names from strings" {
|
"symbol and variable names from strings" {
|
||||||
|
|||||||
@@ -77,7 +77,7 @@ internal fun generateAssembly(
|
|||||||
val errors = ErrorReporterForTests()
|
val errors = ErrorReporterForTests()
|
||||||
determineProgramLoadAddress(program, coptions, errors)
|
determineProgramLoadAddress(program, coptions, errors)
|
||||||
errors.report()
|
errors.report()
|
||||||
val asmgen = AsmGen(program, errors, st, coptions)
|
val asmgen = AsmGen(program, st, coptions, errors)
|
||||||
errors.report()
|
errors.report()
|
||||||
return asmgen.compileToAssembly()
|
return asmgen.compileToAssembly()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,14 +1,6 @@
|
|||||||
%import textio
|
%import textio
|
||||||
%import test_stack
|
|
||||||
%zeropage basicsafe
|
|
||||||
%zpreserved 50,80
|
|
||||||
%zpreserved 150,155
|
|
||||||
%option align_word
|
|
||||||
|
|
||||||
|
|
||||||
main {
|
main {
|
||||||
%option align_word
|
|
||||||
|
|
||||||
ubyte[256] sieve
|
ubyte[256] sieve
|
||||||
ubyte candidate_prime = 2 ; is increased in the loop
|
ubyte candidate_prime = 2 ; is increased in the loop
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,8 @@ include(
|
|||||||
':codeAst',
|
':codeAst',
|
||||||
':compilerAst',
|
':compilerAst',
|
||||||
':codeOptimizers',
|
':codeOptimizers',
|
||||||
|
':virtualmachine',
|
||||||
|
':codeGenVirtual',
|
||||||
':codeGenCpu6502',
|
':codeGenCpu6502',
|
||||||
':codeGenExperimental',
|
':codeGenExperimental',
|
||||||
':compiler',
|
':compiler',
|
||||||
|
|||||||
42
virtualmachine/build.gradle
Normal file
42
virtualmachine/build.gradle
Normal 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!
|
||||||
214
virtualmachine/src/prog8/vm/Assembler.kt
Normal file
214
virtualmachine/src/prog8/vm/Assembler.kt
Normal 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("")
|
||||||
|
}
|
||||||
|
}
|
||||||
86
virtualmachine/src/prog8/vm/GraphicsWindow.kt
Normal file
86
virtualmachine/src/prog8/vm/GraphicsWindow.kt
Normal 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()
|
||||||
|
}
|
||||||
|
}
|
||||||
250
virtualmachine/src/prog8/vm/Instructions.kt
Normal file
250
virtualmachine/src/prog8/vm/Instructions.kt
Normal 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)
|
||||||
|
)
|
||||||
43
virtualmachine/src/prog8/vm/Main.kt
Normal file
43
virtualmachine/src/prog8/vm/Main.kt
Normal 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()
|
||||||
|
}
|
||||||
65
virtualmachine/src/prog8/vm/Memory.kt
Normal file
65
virtualmachine/src/prog8/vm/Memory.kt
Normal 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()
|
||||||
|
}
|
||||||
|
}
|
||||||
24
virtualmachine/src/prog8/vm/Registers.kt
Normal file
24
virtualmachine/src/prog8/vm/Registers.kt
Normal 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]
|
||||||
|
}
|
||||||
81
virtualmachine/src/prog8/vm/SysCalls.kt
Normal file
81
virtualmachine/src/prog8/vm/SysCalls.kt
Normal 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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
647
virtualmachine/src/prog8/vm/VirtualMachine.kt
Normal file
647
virtualmachine/src/prog8/vm/VirtualMachine.kt
Normal 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()
|
||||||
|
}
|
||||||
|
}
|
||||||
13
virtualmachine/virtualmachine.iml
Normal file
13
virtualmachine/virtualmachine.iml
Normal 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>
|
||||||
Reference in New Issue
Block a user