prog8/compiler/src/prog8/stackvm/StackVm.kt
2018-09-15 16:21:05 +02:00

1306 lines
52 KiB
Kotlin

package prog8.stackvm
import prog8.ast.DataType
import prog8.compiler.Mflpt5
import prog8.compiler.Petscii
import java.awt.EventQueue
import java.io.File
import java.io.PrintWriter
import java.util.*
import java.util.regex.Pattern
import javax.swing.Timer
import kotlin.math.max
import kotlin.math.pow
import kotlin.system.exitProcess
enum class Opcode {
// pushing values on the (evaluation) stack
PUSH, // push constant byte value
PUSH_MEM, // push byte value from memory to stack
PUSH_MEM_W, // push word value from memory to stack
PUSH_MEM_F, // push float value from memory to stack
PUSH_VAR, // push a variable
DUP, // push topmost value once again
ARRAY, // create arrayvalue of number of integer values on the stack, given in argument
// popping values off the (evaluation) stack, possibly storing them in another location
DISCARD, // discard top value
POP_MEM, // pop value into destination memory address
POP_VAR, // pop value into variable
// numeric and bitwise arithmetic
ADD,
SUB,
MUL,
DIV,
POW,
NEG,
SHL,
SHL_MEM,
SHL_MEM_W,
SHL_VAR,
SHR,
SHR_MEM,
SHR_MEM_W,
SHR_VAR,
ROL,
ROL_MEM,
ROL_MEM_W,
ROL_VAR,
ROR,
ROR_MEM,
ROR_MEM_W,
ROR_VAR,
ROL2,
ROL2_MEM,
ROL2_MEM_W,
ROL2_VAR,
ROR2,
ROR2_MEM,
ROR2_MEM_W,
ROR2_VAR,
BITAND,
BITOR,
BITXOR,
INV,
LSB,
MSB,
// numeric type conversions not covered by other opcodes
B2WORD, // convert a byte into a word where it is the lower eight bits $00xx
MSB2WORD, // convert a byte into a word where it is the upper eight bits $xx00
B2FLOAT, // convert byte into floating point
W2FLOAT, // convert word into floating point
// logical operations
AND,
OR,
XOR,
// increment, decrement
INC,
INC_MEM,
INC_MEM_W,
INC_VAR,
DEC,
DEC_MEM,
DEC_MEM_W,
DEC_VAR,
// comparisons
LESS,
GREATER,
LESSEQ,
GREATEREQ,
EQUAL,
NOTEQUAL,
// branching
JUMP,
BCS,
BCC,
BEQ, // branch if value on top of stack is zero
BNE, // branch if value on top of stack is not zero
BMI, // branch if value on top of stack < 0
BPL, // branch if value on top of stack >= 0
// BVS, // status flag V (overflow) not implemented
// BVC, // status flag V (overflow) not implemented
// subroutine calling
CALL,
RETURN,
SYSCALL,
// misc
SWAP,
SEC, // set carry status flag NOTE: is mostly fake, carry flag is not affected by any numeric operations
CLC, // clear carry status flag NOTE: is mostly fake, carry flag is not affected by any numeric operations
NOP,
BREAK, // breakpoint
TERMINATE // end the program
}
enum class Syscall(val callNr: Short) {
WRITE_MEMCHR(10), // print a single char from the memory
WRITE_MEMSTR(11), // print a 0-terminated petscii string from the memory
WRITE_NUM(12), // pop from the evaluation stack and print it as a number
WRITE_CHAR(13), // pop from the evaluation stack and print it as a single petscii character
WRITE_VAR(14), // print the number or string from the given variable
INPUT_VAR(15), // user input a string into a variable
GFX_PIXEL(16), // plot a pixel at (x,y,color) pushed on stack in that order
GFX_CLEARSCR(17), // clear the screen with color pushed on stack
GFX_TEXT(18), // write text on screen at (x,y,color,text) pushed on stack in that order
RANDOM(19), // push a random byte on the stack
RANDOM_W(20), // push a random word on the stack
RANDOM_F(21), // push a random float on the stack (between 0.0 and 1.0)
FUNC_P_CARRY(100),
FUNC_P_IRQD(101),
FUNC_ROL(102),
FUNC_ROR(103),
FUNC_ROL2(104),
FUNC_ROR2(105),
FUNC_LSL(106),
FUNC_LSR(107),
FUNC_SIN(108),
FUNC_COS(109),
FUNC_ABS(110),
FUNC_ACOS(111),
FUNC_ASIN(112),
FUNC_TAN(113),
FUNC_ATAN(114),
FUNC_LOG(115),
FUNC_LOG10(116),
FUNC_SQRT(117),
FUNC_RAD(118),
FUNC_DEG(119),
FUNC_ROUND(120),
FUNC_FLOOR(121),
FUNC_CEIL(122),
FUNC_MAX(123),
FUNC_MIN(124),
FUNC_AVG(125),
FUNC_SUM(126),
FUNC_LEN(127),
FUNC_ANY(128),
FUNC_ALL(129),
FUNC_LSB(130),
FUNC_MSB(131)
}
class Memory {
private val mem = ShortArray(65536) // shorts because byte is signed and we store values 0..255
fun getByte(address: Int): Short {
return mem[address]
}
fun setByte(address: Int, value: Short) {
if(value<0 || value>255) throw VmExecutionException("byte value not 0..255")
mem[address] = value
}
fun getWord(address: Int): Int {
return 256*mem[address] + mem[address+1]
}
fun setWord(address: Int, value: Int) {
if(value<0 || value>65535) throw VmExecutionException("word value not 0..65535")
mem[address] = (value / 256).toShort()
mem[address+1] = value.and(255).toShort()
}
fun setFloat(address: Int, value: Double) {
val mflpt5 = Mflpt5.fromNumber(value)
mem[address] = mflpt5.b0
mem[address+1] = mflpt5.b1
mem[address+2] = mflpt5.b2
mem[address+3] = mflpt5.b3
mem[address+4] = mflpt5.b4
}
fun getFloat(address: Int): Double {
return Mflpt5(mem[address], mem[address+1], mem[address+2], mem[address+3], mem[address+4]).toDouble()
}
fun setString(address: Int, str: String) {
// lowercase PETSCII
val petscii = Petscii.encodePetscii(str, true)
var addr = address
for (c in petscii) mem[addr++] = c
mem[addr] = 0
}
fun getString(strAddress: Int): String {
// lowercase PETSCII
val petscii = mutableListOf<Short>()
var addr = strAddress
while(true) {
val byte = mem[addr++]
if(byte==0.toShort()) break
petscii.add(byte)
}
return Petscii.decodePetscii(petscii, true)
}
}
class Value(val type: DataType, private val numericvalue: Number?, val stringvalue: String?=null, val arrayvalue: IntArray?=null) {
private var byteval: Short? = null
private var wordval: Int? = null
private var floatval: Double? = null
val asBooleanValue: Boolean
init {
when(type) {
DataType.BYTE -> {
byteval = (numericvalue!!.toInt() and 255).toShort()
asBooleanValue = byteval != (0.toShort())
}
DataType.WORD -> {
wordval = numericvalue!!.toInt() and 65535
asBooleanValue = wordval != 0
}
DataType.FLOAT -> {
floatval = numericvalue!!.toDouble()
asBooleanValue = floatval != 0.0
}
DataType.ARRAY -> {
asBooleanValue = arrayvalue!!.isNotEmpty()
}
DataType.STR -> {
if(stringvalue==null) throw VmExecutionException("expect stringvalue for STR type")
asBooleanValue = stringvalue.isNotEmpty()
}
else -> throw VmExecutionException("invalid datatype $type")
}
}
override fun toString(): String {
return "$type: $numericvalue $stringvalue"
}
fun numericValue(): Number {
return when(type) {
DataType.BYTE -> byteval!!
DataType.WORD-> wordval!!
DataType.FLOAT -> floatval!!
else -> throw VmExecutionException("invalid datatype for numeric value: $type")
}
}
fun integerValue(): Int {
return when(type) {
DataType.BYTE -> byteval!!.toInt()
DataType.WORD-> wordval!!
DataType.FLOAT -> floatval!!.toInt()
else -> throw VmExecutionException("invalid datatype for integer value: $type")
}
}
fun add(other: Value): Value {
val v1 = numericValue()
val v2 = other.numericValue()
val result = v1.toDouble() + v2.toDouble()
return Value(type, result)
}
fun sub(other: Value): Value {
val v1 = numericValue()
val v2 = other.numericValue()
val result = v1.toDouble() - v2.toDouble()
return Value(type, result)
}
fun mul(other: Value): Value {
val v1 = numericValue()
val v2 = other.numericValue()
val result = v1.toDouble() * v2.toDouble()
return Value(type, result)
}
fun div(other: Value): Value {
val v1 = numericValue()
val v2 = other.numericValue()
val result = v1.toDouble() / v2.toDouble()
return Value(type, result)
}
fun pow(other: Value): Value {
val v1 = numericValue()
val v2 = other.numericValue()
val result = v1.toDouble().pow(v2.toDouble())
return Value(type, result)
}
fun shl(): Value {
val v = integerValue()
return Value(type, v.shl(1))
}
fun shr(): Value {
val v = integerValue()
return Value(type, v.ushr(1))
}
fun rol(carry: Boolean): Pair<Value, Boolean> {
// 9 or 17 bit rotate left (with carry))
return when(type) {
DataType.BYTE -> {
val v = byteval!!.toInt()
val newCarry = (v and 0x80) != 0
val newval = (v and 0x7f shl 1) or (if(carry) 1 else 0)
Pair(Value(DataType.BYTE, newval), newCarry)
}
DataType.WORD -> {
val v = wordval!!
val newCarry = (v and 0x8000) != 0
val newval = (v and 0x7fff shl 1) or (if(carry) 1 else 0)
Pair(Value(DataType.WORD, newval), newCarry)
}
else -> throw VmExecutionException("rol can only work on byte/word")
}
}
fun ror(carry: Boolean): Pair<Value, Boolean> {
// 9 or 17 bit rotate right (with carry)
return when(type) {
DataType.BYTE -> {
val v = byteval!!.toInt()
val newCarry = v and 1 != 0
val newval = (v ushr 1) or (if(carry) 0x80 else 0)
Pair(Value(DataType.BYTE, newval), newCarry)
}
DataType.WORD -> {
val v = wordval!!
val newCarry = v and 1 != 0
val newval = (v ushr 1) or (if(carry) 0x8000 else 0)
Pair(Value(DataType.WORD, newval), newCarry)
}
else -> throw VmExecutionException("ror2 can only work on byte/word")
}
}
fun rol2(): Value {
// 8 or 16 bit rotate left
return when(type) {
DataType.BYTE -> {
val v = byteval!!.toInt()
val carry = (v and 0x80) ushr 7
val newval = (v and 0x7f shl 1) or carry
Value(DataType.BYTE, newval)
}
DataType.WORD -> {
val v = wordval!!
val carry = (v and 0x8000) ushr 15
val newval = (v and 0x7fff shl 1) or carry
Value(DataType.WORD, newval)
}
else -> throw VmExecutionException("rol2 can only work on byte/word")
}
}
fun ror2(): Value {
// 8 or 16 bit rotate right
return when(type) {
DataType.BYTE -> {
val v = byteval!!.toInt()
val carry = v and 1 shl 7
val newval = (v ushr 1) or carry
Value(DataType.BYTE, newval)
}
DataType.WORD -> {
val v = wordval!!
val carry = v and 1 shl 15
val newval = (v ushr 1) or carry
Value(DataType.WORD, newval)
}
else -> throw VmExecutionException("ror2 can only work on byte/word")
}
}
fun neg(): Value {
return when(type) {
DataType.BYTE -> Value(DataType.BYTE, -(byteval!!))
DataType.WORD -> Value(DataType.WORD, -(wordval!!))
DataType.FLOAT -> Value(DataType.FLOAT, -(floatval)!!)
else -> throw VmExecutionException("neg can only work on byte/word/float")
}
}
fun bitand(other: Value): Value {
val v1 = integerValue()
val v2 = other.integerValue()
val result = v1.and(v2)
return Value(type, result)
}
fun bitor(other: Value): Value {
val v1 = integerValue()
val v2 = other.integerValue()
val result = v1.or(v2)
return Value(type, result)
}
fun bitxor(other: Value): Value {
val v1 = integerValue()
val v2 = other.integerValue()
val result = v1.xor(v2)
return Value(type, result)
}
fun and(other: Value) = Value(DataType.BYTE, if(this.asBooleanValue && other.asBooleanValue) 1 else 0)
fun or(other: Value) = Value(DataType.BYTE, if(this.asBooleanValue || other.asBooleanValue) 1 else 0)
fun xor(other: Value) = Value(DataType.BYTE, if(this.asBooleanValue xor other.asBooleanValue) 1 else 0)
fun inv(): Value {
return when(type) {
DataType.BYTE -> Value(DataType.BYTE, byteval!!.toInt().inv())
DataType.WORD -> Value(DataType.WORD, wordval!!.inv())
else -> throw VmExecutionException("not can only work on byte/word")
}
}
fun inc(): Value {
return when(type) {
DataType.BYTE -> Value(DataType.BYTE, (byteval!! + 1).and(255))
DataType.WORD -> Value(DataType.WORD, (wordval!! + 1).and(65535))
DataType.FLOAT -> Value(DataType.FLOAT, floatval!! + 1)
else -> throw VmExecutionException("inc can only work on byte/word/float")
}
}
fun dec(): Value {
return when(type) {
DataType.BYTE -> Value(DataType.BYTE, (byteval!! - 1).and(255))
DataType.WORD -> Value(DataType.WORD, (wordval!! - 1).and(65535))
DataType.FLOAT -> Value(DataType.FLOAT, floatval!! - 1)
else -> throw VmExecutionException("dec can only work on byte/word/float")
}
}
fun lsb(): Value {
return when(type) {
DataType.BYTE -> Value(DataType.BYTE, byteval!!.toInt() and 255)
DataType.WORD -> Value(DataType.WORD, wordval!! and 255)
else -> throw VmExecutionException("not can only work on byte/word")
}
}
fun msb(): Value {
return when(type) {
DataType.BYTE -> Value(DataType.BYTE, byteval!!.toInt() ushr 8 and 255)
DataType.WORD -> Value(DataType.WORD, wordval!! ushr 8 and 255)
else -> throw VmExecutionException("not can only work on byte/word")
}
}
fun compareLess(other: Value) = Value(DataType.BYTE, if(this.numericValue().toDouble() < other.numericValue().toDouble()) 1 else 0)
fun compareGreater(other: Value) = Value(DataType.BYTE, if(this.numericValue().toDouble() > other.numericValue().toDouble()) 1 else 0)
fun compareLessEq(other: Value) = Value(DataType.BYTE, if(this.numericValue().toDouble() <= other.numericValue().toDouble()) 1 else 0)
fun compareGreaterEq(other: Value) = Value(DataType.BYTE, if(this.numericValue().toDouble() >= other.numericValue().toDouble()) 1 else 0)
fun compareEqual(other: Value) = Value(DataType.BYTE, if(this.numericValue() == other.numericValue()) 1 else 0)
fun compareNotEqual(other: Value) = Value(DataType.BYTE, if(this.numericValue() != other.numericValue()) 1 else 0)
}
data class Instruction(val opcode: Opcode,
val arg: Value? = null,
val callArgs: List<Value>? = emptyList(),
val callArgsAllocations: List<String> = emptyList(),
val callLabel: String? = null)
{
lateinit var next: Instruction
var nextAlt: Instruction? = null
init {
if(callLabel!=null) {
if(callArgs!!.size != callArgsAllocations.size)
throw VmExecutionException("for $opcode the callArgsAllocations size is not the same as the callArgs size")
}
}
override fun toString(): String {
return if(callLabel==null)
"$opcode $arg"
else
"$opcode $callLabel $callArgs $callArgsAllocations"
}
}
private class VmExecutionException(msg: String?) : Exception(msg)
private class VmTerminationException(msg: String?) : Exception(msg)
private class VmBreakpointException : Exception("breakpoint")
private class MyStack<T> : Stack<T>() {
fun peek(amount: Int) : List<T> {
return this.toList().subList(max(0, size-amount), size)
}
fun pop2() : Pair<T, T> = Pair(pop(), pop())
fun printTop(amount: Int, output: PrintWriter) {
peek(amount).reversed().forEach { output.println(" $it") }
}
}
class Program (prog: MutableList<Instruction>,
labels: Map<String, Instruction>,
val variables: Map<String, Value>,
val memory: Map<Int, List<Value>>)
{
companion object {
fun load(filename: String): Program {
val lines = File(filename).readLines().withIndex().iterator()
var memory = mapOf<Int, List<Value>>()
var vars = mapOf<String, Value>()
var instructions = mutableListOf<Instruction>()
var labels = mapOf<String, Instruction>()
while(lines.hasNext()) {
val (lineNr, line) = lines.next()
if(line.startsWith(';') || line.isEmpty())
continue
else if(line=="%memory")
memory = loadMemory(lines)
else if(line=="%variables")
vars = loadVars(lines)
else if(line=="%instructions") {
val (insResult, labelResult) = loadInstructions(lines)
instructions = insResult
labels = labelResult
}
else throw VmExecutionException("syntax error at line ${lineNr+1}")
}
return Program(instructions, labels, vars, memory)
}
private fun loadInstructions(lines: Iterator<IndexedValue<String>>): Pair<MutableList<Instruction>, Map<String, Instruction>> {
val instructions = mutableListOf<Instruction>()
val labels = mutableMapOf<String, Instruction>()
val splitpattern = Pattern.compile("\\s+")
var nextInstructionLabelname = ""
while(true) {
val (lineNr, line) = lines.next()
if(line=="%end_instructions")
return Pair(instructions, labels)
if(!line.startsWith(' ') && line.endsWith(':')) {
nextInstructionLabelname = line.substring(0, line.length-1)
} else if(line.startsWith(' ')) {
val parts = line.trimStart().split(splitpattern, limit = 2)
val opcode=Opcode.valueOf(parts[0].toUpperCase())
val args = if(parts.size==2) parts[1] else null
val instruction = when(opcode) {
Opcode.JUMP, Opcode.CALL, Opcode.BMI, Opcode.BPL,
Opcode.BEQ, Opcode.BNE, Opcode.BCS, Opcode.BCC -> {
if(args!!.startsWith('$')) {
Instruction(opcode, Value(DataType.WORD, args.substring(1).toInt(16)))
} else {
Instruction(opcode, callLabel = args)
}
}
Opcode.INC_VAR, Opcode.DEC_VAR,
Opcode.SHR_VAR, Opcode.SHL_VAR, Opcode.ROL_VAR, Opcode.ROR_VAR,
Opcode.ROL2_VAR, Opcode.ROR2_VAR, Opcode.POP_VAR, Opcode.PUSH_VAR -> {
val withoutQuotes =
if(args!!.startsWith('"') && args.endsWith('"'))
args.substring(1, args.length-1) else args
Instruction(opcode, Value(DataType.STR, null, withoutQuotes))
}
Opcode.SYSCALL -> {
val syscallparts = args!!.split(' ')
val call = Syscall.valueOf(syscallparts[0])
val callValue = if(syscallparts.size==2) getArgValue(syscallparts[1]) else null
val callValues = if(callValue==null) emptyList() else listOf(callValue)
Instruction(opcode, Value(DataType.BYTE, call.callNr), callValues)
}
else -> {
// println("INSTR $opcode at $lineNr args=$args")
Instruction(opcode, getArgValue(args))
}
}
instructions.add(instruction)
if(nextInstructionLabelname.isNotEmpty()) {
labels[nextInstructionLabelname] = instruction
nextInstructionLabelname = ""
}
}
}
}
private fun getArgValue(args: String?): Value? {
if(args==null)
return null
if(args[0]=='"' && args[args.length-1]=='"') {
// it's a string.
return Value(DataType.STR, null, unescape(args.substring(1, args.length-1)))
}
val (type, valueStr) = args.split(':')
return when(type) {
"b" -> Value(DataType.BYTE, valueStr.toShort(16))
"w" -> Value(DataType.WORD, valueStr.toInt(16))
"f" -> Value(DataType.FLOAT, valueStr.toDouble())
else -> throw VmExecutionException("invalid datatype $type")
}
}
private fun loadVars(lines: Iterator<IndexedValue<String>>): Map<String, Value> {
val vars = mutableMapOf<String, Value>()
val splitpattern = Pattern.compile("\\s+")
while(true) {
val (lineNr, line) = lines.next()
if(line=="%end_variables")
return vars
val (name, type, valueStr) = line.split(splitpattern, limit = 3)
val value = when(type) {
"byte" -> Value(DataType.BYTE, valueStr.toShort(16))
"word" -> Value(DataType.WORD, valueStr.toInt(16))
"float" -> Value(DataType.FLOAT, valueStr.toDouble())
"str" -> {
if(valueStr.startsWith('"') && valueStr.endsWith('"'))
Value(DataType.STR, null, unescape(valueStr.substring(1, valueStr.length-1)))
else
throw VmExecutionException("str should be enclosed in quotes at line ${lineNr+1}")
}
else -> throw VmExecutionException("invalid datatype at line ${lineNr+1}")
}
vars[name] = value
}
}
private fun unescape(st: String): String {
val result = mutableListOf<Char>()
val iter = st.iterator()
while(iter.hasNext()) {
val c = iter.nextChar()
if(c=='\\') {
val ec = iter.nextChar()
result.add(when(ec) {
'\\' -> '\\'
'b' -> '\b'
'n' -> '\n'
'r' -> '\r'
't' -> '\t'
'u' -> {
"${iter.nextChar()}${iter.nextChar()}${iter.nextChar()}${iter.nextChar()}".toInt(16).toChar()
}
else -> throw VmExecutionException("invalid escape char: $ec")
})
} else {
result.add(c)
}
}
return result.joinToString("")
}
private fun loadMemory(lines: Iterator<IndexedValue<String>>): Map<Int, List<Value>> {
val memory = mutableMapOf<Int, List<Value>>()
while(true) {
val (lineNr, line) = lines.next()
if(line=="%end_memory")
return memory
val address = line.substringBefore(' ').toInt(16)
val rest = line.substringAfter(' ').trim()
if(rest.startsWith('"')) {
memory[address] = listOf(Value(DataType.STR, null, unescape(rest.substring(1, rest.length - 1))))
} else {
val valueStrings = rest.split(' ')
val values = mutableListOf<Value>()
valueStrings.forEach {
when(it.length) {
2 -> values.add(Value(DataType.BYTE, it.toShort(16)))
4 -> values.add(Value(DataType.WORD, it.toInt(16)))
else -> throw VmExecutionException("invalid value at line $lineNr+1")
}
}
memory[address] = values
}
}
}
}
val program: List<Instruction>
init {
prog.add(Instruction(Opcode.TERMINATE))
prog.add(Instruction(Opcode.NOP))
program = prog
connect(labels)
}
private fun connect(labels: Map<String, Instruction>) {
val it1 = program.iterator()
val it2 = program.iterator()
it2.next()
while(it1.hasNext() && it2.hasNext()) {
val instr = it1.next()
val nextInstr = it2.next()
when(instr.opcode) {
Opcode.TERMINATE -> instr.next = instr // won't ever execute a next instruction
Opcode.RETURN -> instr.next = instr // kinda a special one, in actuality the return instruction is dynamic
Opcode.JUMP -> {
if(instr.callLabel==null) {
throw VmExecutionException("stackVm doesn't support JUMP to memory address")
} else {
// jump to label
val target = labels[instr.callLabel] ?: throw VmExecutionException("undefined label: ${instr.callLabel}")
instr.next = target
}
}
Opcode.BCC, Opcode.BCS, Opcode.BEQ, Opcode.BNE, Opcode.BMI, Opcode.BPL -> {
if(instr.callLabel==null) {
throw VmExecutionException("stackVm doesn't support branch to memory address")
} else {
// branch to label
val jumpInstr = labels[instr.callLabel] ?: throw VmExecutionException("undefined label: ${instr.callLabel}")
instr.next = jumpInstr
instr.nextAlt = nextInstr
}
}
Opcode.CALL -> {
if(instr.callLabel==null) {
throw VmExecutionException("stackVm doesn't support CALL to memory address")
} else {
// call label
val jumpInstr = labels[instr.callLabel] ?: throw VmExecutionException("undefined label: ${instr.callLabel}")
instr.next = jumpInstr
instr.nextAlt = nextInstr // instruction to return to
}
}
else -> instr.next = nextInstr
}
}
}
}
class StackVm(val traceOutputFile: String?) {
private val mem = Memory()
private val evalstack = MyStack<Value>() // evaluation stack
private val callstack = MyStack<Instruction>() // subroutine call stack
private var variables = mutableMapOf<String, Value>() // all variables (set of all vars used by all blocks/subroutines) key = their fully scoped name
private var carry: Boolean = false
private var program = listOf<Instruction>()
private var traceOutput = if(traceOutputFile!=null) File(traceOutputFile).printWriter() else null
private lateinit var currentIns: Instruction
private lateinit var canvas: BitmapScreenPanel
private val rnd = Random()
fun load(program: Program, canvas: BitmapScreenPanel) {
this.program = program.program
this.canvas = canvas
this.variables = program.variables.toMutableMap()
if(this.variables.contains("A") ||
this.variables.contains("X") ||
this.variables.contains("Y") ||
this.variables.contains("XY") ||
this.variables.contains("AX") ||
this.variables.contains("AY"))
throw VmExecutionException("program contains variable(s) for the reserved registers A,X,...")
// define the 'registers'
this.variables["A"] = Value(DataType.BYTE, 0)
this.variables["X"] = Value(DataType.BYTE, 0)
this.variables["Y"] = Value(DataType.BYTE, 0)
this.variables["AX"] = Value(DataType.WORD, 0)
this.variables["AY"] = Value(DataType.WORD, 0)
this.variables["XY"] = Value(DataType.WORD, 0)
initMemory(program.memory)
currentIns = this.program[0]
}
fun step() {
// step is invoked every 1/100 sec
// we execute 5000 instructions in one go so we end up doing 500.000 instructions per second
val instructionsPerStep = 5000
val start = System.currentTimeMillis()
for(i:Int in 0..instructionsPerStep) {
try {
currentIns = dispatch(currentIns)
if (evalstack.size > 128)
throw VmExecutionException("too many values on evaluation stack")
if (callstack.size > 128)
throw VmExecutionException("too many nested/recursive calls")
} catch (bp: VmBreakpointException) {
currentIns = currentIns.next
throw bp
}
}
val time = System.currentTimeMillis()-start
if(time > 100) {
println("WARNING: vm dispatch step took > 100 msec")
}
}
private fun initMemory(memory: Map<Int, List<Value>>) {
for (meminit in memory) {
var address = meminit.key
for (value in meminit.value) {
when(value.type) {
DataType.BYTE -> {
mem.setByte(address, value.integerValue().toShort())
address += 1
}
DataType.WORD -> {
mem.setWord(address, value.integerValue())
address += 2
}
DataType.FLOAT -> {
mem.setFloat(address, value.numericValue().toDouble())
address += 5
}
DataType.STR -> {
mem.setString(address, value.stringvalue!!)
address += value.stringvalue.length+1
}
else -> throw VmExecutionException("invalid mem datatype ${value.type}")
}
}
}
}
private fun dispatch(ins: Instruction) : Instruction {
traceOutput?.println("\n$ins")
when (ins.opcode) {
Opcode.NOP -> {}
Opcode.PUSH -> evalstack.push(ins.arg)
Opcode.PUSH_MEM -> {
val address = ins.arg!!.integerValue()
evalstack.push(Value(DataType.BYTE, mem.getByte(address)))
}
Opcode.PUSH_MEM_W -> {
val address = ins.arg!!.integerValue()
evalstack.push(Value(DataType.WORD, mem.getWord(address)))
}
Opcode.PUSH_MEM_F -> {
val address = ins.arg!!.integerValue()
evalstack.push(Value(DataType.FLOAT, mem.getFloat(address)))
}
Opcode.DUP -> evalstack.push(evalstack.peek())
Opcode.ARRAY -> {
val amount = ins.arg!!.integerValue()
val array = mutableListOf<Int>()
for (i in 0..amount) {
val value = evalstack.pop()
if(value.type!=DataType.BYTE && value.type!=DataType.WORD)
throw VmExecutionException("array requires values to be all byte/word")
array.add(value.integerValue())
}
evalstack.push(Value(DataType.ARRAY, null, arrayvalue = array.toIntArray()))
}
Opcode.DISCARD -> evalstack.pop()
Opcode.SWAP -> {
val (top, second) = evalstack.pop2()
evalstack.push(top)
evalstack.push(second)
}
Opcode.POP_MEM -> {
val value = evalstack.pop()
val address = ins.arg!!.integerValue()
when (value.type) {
DataType.BYTE -> mem.setByte(address, value.integerValue().toShort())
DataType.WORD -> mem.setWord(address, value.integerValue())
DataType.FLOAT -> mem.setFloat(address, value.numericValue().toDouble())
else -> throw VmExecutionException("can only manipulate byte/word/float on stack")
}
}
Opcode.ADD -> {
val (top, second) = evalstack.pop2()
evalstack.push(second.add(top))
}
Opcode.SUB -> {
val (top, second) = evalstack.pop2()
evalstack.push(second.sub(top))
}
Opcode.MUL -> {
val (top, second) = evalstack.pop2()
evalstack.push(second.mul(top))
}
Opcode.DIV -> {
val (top, second) = evalstack.pop2()
evalstack.push(second.div(top))
}
Opcode.POW -> {
val (top, second) = evalstack.pop2()
evalstack.push(second.pow(top))
}
Opcode.NEG -> {
val v = evalstack.pop()
evalstack.push(v.neg())
}
Opcode.SHL -> {
val v = evalstack.pop()
evalstack.push(v.shl())
}
Opcode.SHR -> {
val v = evalstack.pop()
evalstack.push(v.shr())
}
Opcode.ROL -> {
val v = evalstack.pop()
val (result, newCarry) = v.rol(carry)
this.carry = newCarry
evalstack.push(result)
}
Opcode.ROL2 -> {
val v = evalstack.pop()
evalstack.push(v.rol2())
}
Opcode.ROR -> {
val v = evalstack.pop()
val (result, newCarry) = v.ror(carry)
this.carry = newCarry
evalstack.push(result)
}
Opcode.ROR2 -> {
val v = evalstack.pop()
evalstack.push(v.ror2())
}
Opcode.BITAND -> {
val (top, second) = evalstack.pop2()
evalstack.push(second.bitand(top))
}
Opcode.BITOR -> {
val (top, second) = evalstack.pop2()
evalstack.push(second.bitor(top))
}
Opcode.BITXOR -> {
val (top, second) = evalstack.pop2()
evalstack.push(second.bitxor(top))
}
Opcode.INV -> {
val v = evalstack.pop()
evalstack.push(v.inv())
}
Opcode.INC -> {
val v = evalstack.pop()
evalstack.push(v.inc())
}
Opcode.DEC -> {
val v = evalstack.pop()
evalstack.push(v.dec())
}
Opcode.SYSCALL -> {
val callId = ins.arg!!.integerValue().toShort()
val syscall = Syscall.values().first { it.callNr == callId }
when (syscall) {
Syscall.WRITE_MEMCHR -> print(Petscii.decodePetscii(listOf(mem.getByte(ins.callArgs!![0].integerValue())), true))
Syscall.WRITE_MEMSTR -> print(mem.getString(ins.callArgs!![0].integerValue()))
Syscall.WRITE_NUM -> print(evalstack.pop().numericValue())
Syscall.WRITE_CHAR -> print(Petscii.decodePetscii(listOf(evalstack.pop().integerValue().toShort()), true))
Syscall.WRITE_VAR -> {
val varname = ins.callArgs!![0].stringvalue ?: throw VmExecutionException("$syscall expects string argument (the variable name)")
val variable = variables[varname] ?: throw VmExecutionException("unknown variable: $varname")
when(variable.type) {
DataType.BYTE, DataType.WORD, DataType.FLOAT -> print(variable.numericValue())
DataType.STR -> print(variable.stringvalue)
else -> throw VmExecutionException("invalid datatype")
}
}
Syscall.INPUT_VAR -> {
val varname = ins.callArgs!![0].stringvalue ?: throw VmExecutionException("$syscall expects string argument (the variable name)")
val variable = variables[varname] ?: throw VmExecutionException("unknown variable: $varname")
val input = readLine() ?: throw VmExecutionException("expected user input")
val value = when(variable.type) {
DataType.BYTE -> Value(DataType.BYTE, input.toShort())
DataType.WORD -> Value(DataType.WORD, input.toInt())
DataType.FLOAT -> Value(DataType.FLOAT, input.toDouble())
DataType.STR -> Value(DataType.STR, null, input)
else -> throw VmExecutionException("invalid datatype")
}
variables[varname] = value
}
Syscall.GFX_PIXEL -> {
// plot pixel at (x, y, color) from stack
val color = evalstack.pop()
val (y, x) = evalstack.pop2()
canvas.setPixel(x.integerValue(), y.integerValue(), color.integerValue())
}
Syscall.GFX_CLEARSCR -> {
val color = evalstack.pop()
canvas.clearScreen(color.integerValue())
}
Syscall.GFX_TEXT -> {
val text = evalstack.pop()
val color = evalstack.pop()
val (y, x) = evalstack.pop2()
canvas.writeText(x.integerValue(), y.integerValue(), text.stringvalue!!, color.integerValue())
}
Syscall.RANDOM -> evalstack.push(Value(DataType.BYTE, rnd.nextInt() and 255))
Syscall.RANDOM_W -> evalstack.push(Value(DataType.WORD, rnd.nextInt() and 65535))
Syscall.RANDOM_F -> evalstack.push(Value(DataType.FLOAT, rnd.nextDouble()))
else -> throw VmExecutionException("unimplemented syscall $syscall")
}
}
Opcode.SEC -> carry = true
Opcode.CLC -> carry = false
Opcode.TERMINATE -> throw VmTerminationException("execution terminated")
Opcode.BREAK -> throw VmBreakpointException()
Opcode.INC_MEM -> {
val addr = ins.arg!!.integerValue()
val newValue = Value(DataType.BYTE, mem.getByte(addr)).inc()
mem.setByte(addr, newValue.integerValue().toShort())
}
Opcode.INC_MEM_W -> {
val addr = ins.arg!!.integerValue()
val newValue = Value(DataType.WORD, mem.getWord(addr)).inc()
mem.setWord(addr, newValue.integerValue())
}
Opcode.DEC_MEM -> {
val addr = ins.arg!!.integerValue()
val newValue = Value(DataType.BYTE, mem.getByte(addr)).dec()
mem.setByte(addr, newValue.integerValue().toShort())
}
Opcode.DEC_MEM_W -> {
val addr = ins.arg!!.integerValue()
val newValue = Value(DataType.WORD, mem.getWord(addr)).dec()
mem.setWord(addr, newValue.integerValue())
}
Opcode.SHL_MEM -> {
val addr = ins.arg!!.integerValue()
val value = Value(DataType.BYTE, mem.getByte(addr))
val newValue = value.shl()
mem.setByte(addr, newValue.integerValue().toShort())
}
Opcode.SHL_MEM_W -> {
val addr = ins.arg!!.integerValue()
val value = Value(DataType.WORD, mem.getWord(addr))
val newValue = value.shl()
mem.setWord(addr, newValue.integerValue())
}
Opcode.SHR_MEM -> {
val addr = ins.arg!!.integerValue()
val value = Value(DataType.BYTE, mem.getByte(addr))
val newValue = value.shr()
mem.setByte(addr, newValue.integerValue().toShort())
}
Opcode.SHR_MEM_W -> {
val addr = ins.arg!!.integerValue()
val value = Value(DataType.WORD, mem.getWord(addr))
val newValue = value.shr()
mem.setWord(addr, newValue.integerValue())
}
Opcode.ROL_MEM -> {
val addr = ins.arg!!.integerValue()
val value = Value(DataType.BYTE, mem.getByte(addr))
val (newValue, newCarry) = value.rol(carry)
mem.setByte(addr, newValue.integerValue().toShort())
carry = newCarry
}
Opcode.ROL_MEM_W -> {
val addr = ins.arg!!.integerValue()
val value = Value(DataType.WORD, mem.getWord(addr))
val (newValue, newCarry) = value.rol(carry)
mem.setWord(addr, newValue.integerValue())
carry = newCarry
}
Opcode.ROR_MEM -> {
val addr = ins.arg!!.integerValue()
val value = Value(DataType.BYTE, mem.getByte(addr))
val (newValue, newCarry) = value.ror(carry)
mem.setByte(addr, newValue.integerValue().toShort())
carry = newCarry
}
Opcode.ROR_MEM_W -> {
val addr = ins.arg!!.integerValue()
val value = Value(DataType.WORD, mem.getWord(addr))
val (newValue, newCarry) = value.ror(carry)
mem.setWord(addr, newValue.integerValue())
carry = newCarry
}
Opcode.ROL2_MEM -> {
val addr = ins.arg!!.integerValue()
val value = Value(DataType.BYTE, mem.getByte(addr))
val newValue = value.rol2()
mem.setByte(addr, newValue.integerValue().toShort())
}
Opcode.ROL2_MEM_W -> {
val addr = ins.arg!!.integerValue()
val value = Value(DataType.WORD, mem.getWord(addr))
val newValue = value.rol2()
mem.setWord(addr, newValue.integerValue())
}
Opcode.ROR2_MEM -> {
val addr = ins.arg!!.integerValue()
val value = Value(DataType.BYTE, mem.getByte(addr))
val newValue = value.ror2()
mem.setByte(addr, newValue.integerValue().toShort())
}
Opcode.ROR2_MEM_W -> {
val addr = ins.arg!!.integerValue()
val value = Value(DataType.WORD, mem.getWord(addr))
val newValue = value.ror2()
mem.setWord(addr, newValue.integerValue())
}
Opcode.JUMP -> {} // do nothing; the next instruction is wired up already to the jump target
Opcode.BCS -> return if(carry) ins.next else ins.nextAlt!!
Opcode.BCC -> return if(carry) ins.nextAlt!! else ins.next
Opcode.BEQ -> return if(evalstack.pop().numericValue().toDouble()==0.0) ins.next else ins.nextAlt!!
Opcode.BNE -> return if(evalstack.pop().numericValue().toDouble()!=0.0) ins.next else ins.nextAlt!!
Opcode.BMI -> return if(evalstack.pop().numericValue().toDouble()<0.0) ins.next else ins.nextAlt!!
Opcode.BPL -> return if(evalstack.pop().numericValue().toDouble()>=0.0) ins.next else ins.nextAlt!!
Opcode.CALL -> callstack.push(ins.nextAlt)
Opcode.RETURN -> return callstack.pop()
Opcode.PUSH_VAR -> {
val varname = ins.arg!!.stringvalue ?: throw VmExecutionException("${ins.opcode} expects string argument (the variable name)")
val variable = variables[varname] ?: throw VmExecutionException("unknown variable: $varname")
evalstack.push(variable)
}
Opcode.POP_VAR -> {
val value = evalstack.pop()
val varname = ins.arg!!.stringvalue ?: throw VmExecutionException("${ins.opcode} expects string argument (the variable name)")
val variable = variables[varname] ?: throw VmExecutionException("unknown variable: $varname")
if(variable.type!=value.type) throw VmExecutionException("value datatype ${value.type} is not the same as variable datatype ${variable.type}")
variables[varname] = value
}
Opcode.SHL_VAR -> {
val varname = ins.arg!!.stringvalue ?: throw VmExecutionException("${ins.opcode} expects string argument (the variable name)")
val variable = variables[varname] ?: throw VmExecutionException("unknown variable: $varname")
variables[varname] = variable.shl()
}
Opcode.SHR_VAR -> {
val varname = ins.arg!!.stringvalue ?: throw VmExecutionException("${ins.opcode} expects string argument (the variable name)")
val variable = variables[varname] ?: throw VmExecutionException("unknown variable: $varname")
variables[varname] = variable.shr()
}
Opcode.ROL_VAR -> {
val varname = ins.arg!!.stringvalue ?: throw VmExecutionException("${ins.opcode} expects string argument (the variable name)")
val variable = variables[varname] ?: throw VmExecutionException("unknown variable: $varname")
val (newValue, newCarry) = variable.rol(carry)
variables[varname] = newValue
carry = newCarry
}
Opcode.ROR_VAR -> {
val varname = ins.arg!!.stringvalue ?: throw VmExecutionException("${ins.opcode} expects string argument (the variable name)")
val variable = variables[varname] ?: throw VmExecutionException("unknown variable: $varname")
val (newValue, newCarry) = variable.ror(carry)
variables[varname] = newValue
carry = newCarry
}
Opcode.ROL2_VAR -> {
val varname = ins.arg!!.stringvalue ?: throw VmExecutionException("${ins.opcode} expects string argument (the variable name)")
val variable = variables[varname] ?: throw VmExecutionException("unknown variable: $varname")
variables[varname] = variable.rol2()
}
Opcode.ROR2_VAR -> {
val varname = ins.arg!!.stringvalue ?: throw VmExecutionException("${ins.opcode} expects string argument (the variable name)")
val variable = variables[varname] ?: throw VmExecutionException("unknown variable: $varname")
variables[varname] = variable.ror2()
}
Opcode.INC_VAR -> {
val varname = ins.arg!!.stringvalue ?: throw VmExecutionException("${ins.opcode} expects string argument (the variable name)")
val variable = variables[varname] ?: throw VmExecutionException("unknown variable: $varname")
variables[varname] = variable.inc()
}
Opcode.DEC_VAR -> {
val varname = ins.arg!!.stringvalue ?: throw VmExecutionException("${ins.opcode} expects string argument (the variable name)")
val variable = variables[varname] ?: throw VmExecutionException("unknown variable: $varname")
variables[varname] = variable.dec()
}
Opcode.LSB -> {
val v = evalstack.pop()
evalstack.push(v.lsb())
}
Opcode.MSB -> {
val v = evalstack.pop()
evalstack.push(v.msb())
}
Opcode.AND -> {
val (top, second) = evalstack.pop2()
evalstack.push(second.and(top))
}
Opcode.OR -> {
val (top, second) = evalstack.pop2()
evalstack.push(second.or(top))
}
Opcode.XOR -> {
val (top, second) = evalstack.pop2()
evalstack.push(second.xor(top))
}
Opcode.LESS -> {
val (top, second) = evalstack.pop2()
evalstack.push(second.compareLess(top))
}
Opcode.GREATER -> {
val (top, second) = evalstack.pop2()
evalstack.push(second.compareGreater(top))
}
Opcode.LESSEQ -> {
val (top, second) = evalstack.pop2()
evalstack.push(second.compareLessEq(top))
}
Opcode.GREATEREQ -> {
val (top, second) = evalstack.pop2()
evalstack.push(second.compareGreaterEq(top))
}
Opcode.EQUAL -> {
val (top, second) = evalstack.pop2()
evalstack.push(second.compareEqual(top))
}
Opcode.NOTEQUAL -> {
val (top, second) = evalstack.pop2()
evalstack.push(second.compareNotEqual(top))
}
Opcode.B2WORD -> {
val byte = evalstack.pop()
if(byte.type==DataType.BYTE) {
evalstack.push(Value(DataType.WORD, byte.integerValue()))
} else {
throw VmExecutionException("attempt to make a word from a non-byte value $byte")
}
}
Opcode.MSB2WORD -> {
val byte = evalstack.pop()
if(byte.type==DataType.BYTE) {
evalstack.push(Value(DataType.WORD, byte.integerValue() * 256))
} else {
throw VmExecutionException("attempt to make a word from a non-byte value $byte")
}
}
Opcode.B2FLOAT -> {
val byte = evalstack.pop()
if(byte.type==DataType.BYTE) {
evalstack.push(Value(DataType.FLOAT, byte.integerValue()))
} else {
throw VmExecutionException("attempt to make a float from a non-byte value $byte")
}
}
Opcode.W2FLOAT -> {
val byte = evalstack.pop()
if(byte.type==DataType.WORD) {
evalstack.push(Value(DataType.FLOAT, byte.integerValue()))
} else {
throw VmExecutionException("attempt to make a float from a non-word value $byte")
}
}
else -> throw VmExecutionException("unimplemented opcode: ${ins.opcode}")
}
if(traceOutput!=null) {
traceOutput?.println(" evalstack (size=${evalstack.size}):")
evalstack.printTop(4, traceOutput!!)
}
return ins.next
}
}
fun main(args: Array<String>) {
println("\nProg8 StackVM by Irmen de Jong (irmen@razorvine.net)")
println("This software is licensed under the GNU GPL 3.0, see https://www.gnu.org/licenses/gpl.html\n")
if(args.size != 1) {
System.err.println("requires one argument: name of stackvm sourcecode file")
exitProcess(1)
}
val program = Program.load(args.first())
val vm = StackVm(traceOutputFile = null)
val dialog = ScreenDialog()
vm.load(program, dialog.canvas)
EventQueue.invokeLater {
dialog.pack()
dialog.isVisible = true
dialog.start()
val programTimer = Timer(10) { _ ->
try {
vm.step()
} catch(bp: VmBreakpointException) {
println("Breakpoint: execution halted. Press enter to resume.")
readLine()
}
}
programTimer.start()
}
}