unit tests for most of the StackVM opcodes. Fixed some opcode behaviors.

This commit is contained in:
Irmen de Jong 2018-09-21 02:20:37 +02:00
parent 455f60fb84
commit 2f48406aad
6 changed files with 1349 additions and 75 deletions

View File

@ -430,7 +430,7 @@ class AstChecker(private val namespace: INameScope, private val compilerOptions:
val to = range.to.constValue(namespace)
var step = 1
if(range.step!=null) {
val stepLv = range.step?.constValue(namespace) ?: LiteralValue(DataType.BYTE, 1, position = range.position)
val stepLv = range.step.constValue(namespace) ?: LiteralValue(DataType.BYTE, 1, position = range.position)
if (stepLv.asIntegerValue == null || stepLv.asIntegerValue == 0) {
err("range step must be an integer != 0")
return range

View File

@ -1,6 +1,5 @@
package prog8.compiler
import jdk.nashorn.internal.ir.JumpStatement
import prog8.ast.*
import prog8.stackvm.*
import java.io.PrintStream

View File

@ -195,7 +195,7 @@ class ConstantFolding(private val namespace: INameScope) : IAstProcessor {
override fun process(range: RangeExpr): IExpression {
range.from = range.from.process(this)
range.to = range.to.process(this)
range.step = range.step?.process(this)
range.step = range.step.process(this)
return super.process(range)
}

View File

@ -10,12 +10,12 @@ import javax.swing.Timer
class BitmapScreenPanel : JPanel() {
private val image = BufferedImage(ScreenWidth, ScreenHeight, BufferedImage.TYPE_INT_ARGB)
private val image = BufferedImage(SCREENWIDTH, SCREENHEIGHT, BufferedImage.TYPE_INT_ARGB)
private val g2d = image.graphics as Graphics2D
init {
val size = Dimension(image.width * Scaling, image.height * Scaling)
val size = Dimension(image.width * SCALING, image.height * SCALING)
minimumSize = size
maximumSize = size
preferredSize = size
@ -32,7 +32,7 @@ class BitmapScreenPanel : JPanel() {
fun clearScreen(color: Int) {
g2d.background = palette[color and 15]
g2d.clearRect(0, 0, BitmapScreenPanel.ScreenWidth, BitmapScreenPanel.ScreenHeight)
g2d.clearRect(0, 0, BitmapScreenPanel.SCREENWIDTH, BitmapScreenPanel.SCREENHEIGHT)
}
fun setPixel(x: Int, y: Int, color: Int) {
image.setRGB(x, y, palette[color and 15].rgb)
@ -44,9 +44,9 @@ class BitmapScreenPanel : JPanel() {
}
companion object {
const val ScreenWidth = 320
const val ScreenHeight = 256
const val Scaling = 3
const val SCREENWIDTH = 320
const val SCREENHEIGHT = 256
const val SCALING = 3
val palette = listOf( // this is Pepto's Commodore-64 palette http://www.pepto.de/projects/colorvic/
Color(0x000000), // 0 = black
Color(0xFFFFFF), // 1 = white
@ -93,19 +93,19 @@ class ScreenDialog : JFrame() {
// the borders (top, left, right, bottom)
val borderTop = JPanel().apply {
preferredSize = Dimension(BitmapScreenPanel.Scaling * (BitmapScreenPanel.ScreenWidth+2*borderWidth), BitmapScreenPanel.Scaling * borderWidth)
preferredSize = Dimension(BitmapScreenPanel.SCALING * (BitmapScreenPanel.SCREENWIDTH+2*borderWidth), BitmapScreenPanel.SCALING * borderWidth)
background = BitmapScreenPanel.palette[14]
}
val borderBottom = JPanel().apply {
preferredSize =Dimension(BitmapScreenPanel.Scaling * (BitmapScreenPanel.ScreenWidth+2*borderWidth), BitmapScreenPanel.Scaling * borderWidth)
preferredSize =Dimension(BitmapScreenPanel.SCALING * (BitmapScreenPanel.SCREENWIDTH+2*borderWidth), BitmapScreenPanel.SCALING * borderWidth)
background = BitmapScreenPanel.palette[14]
}
val borderLeft = JPanel().apply {
preferredSize =Dimension(BitmapScreenPanel.Scaling * borderWidth, BitmapScreenPanel.Scaling * BitmapScreenPanel.ScreenHeight)
preferredSize =Dimension(BitmapScreenPanel.SCALING * borderWidth, BitmapScreenPanel.SCALING * BitmapScreenPanel.SCREENHEIGHT)
background = BitmapScreenPanel.palette[14]
}
val borderRight = JPanel().apply {
preferredSize =Dimension(BitmapScreenPanel.Scaling * borderWidth, BitmapScreenPanel.Scaling * BitmapScreenPanel.ScreenHeight)
preferredSize =Dimension(BitmapScreenPanel.SCALING * borderWidth, BitmapScreenPanel.SCALING * BitmapScreenPanel.SCREENHEIGHT)
background = BitmapScreenPanel.palette[14]
}
c = GridBagConstraints()

View File

@ -117,6 +117,8 @@ enum class Opcode {
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
SEI, // set irq-disable status flag
CLI, // clear irq-disable status flag
NOP,
BREAKPOINT, // breakpoint
TERMINATE, // end the program
@ -134,8 +136,6 @@ enum class Syscall(val callNr: Short) {
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
FUNC_P_CARRY(64),
FUNC_P_IRQD(65),
FUNC_SIN(66),
FUNC_COS(67),
FUNC_ABS(68),
@ -162,10 +162,9 @@ enum class Syscall(val callNr: Short) {
FUNC_RND(89), // push a random byte on the stack
FUNC_RNDW(90), // push a random word on the stack
FUNC_RNDF(91), // push a random float on the stack (between 0.0 and 1.0)
FUNC_FLT(92)
// note: not all builtin functions of the Prog8 language are present as functions:
// some of them are already opcodes (such as MSB and ROL)!
// some of them are already opcodes (such as MSB and ROL and FLT)!
}
class Memory {
@ -181,13 +180,13 @@ class Memory {
}
fun getWord(address: Int): Int {
return 256*mem[address] + mem[address+1]
return mem[address] + 256*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()
mem[address] = value.and(255).toShort()
mem[address+1] = (value / 256).toShort()
}
fun setFloat(address: Int, value: Double) {
@ -222,6 +221,10 @@ class Memory {
}
return Petscii.decodePetscii(petscii, true)
}
fun clear() {
for(i in 0..65535) mem[i]=0
}
}
@ -234,18 +237,22 @@ class Value(val type: DataType, numericvalue: Number?, val stringvalue: String?=
init {
when(type) {
DataType.BYTE -> {
byteval = (numericvalue!!.toInt() and 255).toShort()
byteval = numericvalue!!.toShort()
if(byteval!! <0 || byteval!! > 255)
throw VmExecutionException("byte value overflow: $byteval")
asBooleanValue = byteval != (0.toShort())
}
DataType.WORD -> {
wordval = numericvalue!!.toInt() and 65535
wordval = numericvalue!!.toInt()
if(wordval!! <0 || wordval!! > 65535)
throw VmExecutionException("word value overflow: $wordval")
asBooleanValue = wordval != 0
}
DataType.FLOAT -> {
floatval = numericvalue!!.toDouble()
asBooleanValue = floatval != 0.0
}
DataType.ARRAY -> {
DataType.ARRAY, DataType.ARRAY_W -> {
asBooleanValue = arrayvalue!!.isNotEmpty()
}
DataType.STR, DataType.STR_P, DataType.STR_S, DataType.STR_PS -> {
@ -262,9 +269,9 @@ class Value(val type: DataType, numericvalue: Number?, val stringvalue: String?=
DataType.WORD -> "w:%04x".format(wordval)
DataType.FLOAT -> "f:$floatval"
DataType.STR, DataType.STR_P, DataType.STR_S, DataType.STR_PS -> "\"$stringvalue\""
DataType.ARRAY -> TODO("array")
DataType.ARRAY_W -> TODO("word array")
DataType.MATRIX -> TODO("matrix")
DataType.ARRAY -> TODO("tostring array")
DataType.ARRAY_W -> TODO("tostring word array")
DataType.MATRIX -> TODO("tostring matrix")
}
}
@ -286,13 +293,23 @@ class Value(val type: DataType, numericvalue: Number?, val stringvalue: String?=
}
}
override fun hashCode(): Int {
val bh = byteval?.hashCode() ?: 0x10001234
val wh = wordval?.hashCode() ?: 0x01002345
val fh = floatval?.hashCode() ?: 0x00103456
val ah = arrayvalue?.hashCode() ?: 0x11119876
return bh xor wh xor fh xor ah xor type.hashCode()
}
override fun equals(other: Any?): Boolean {
if(other==null || other !is Value)
return false
return compareTo(other)==0
return compareTo(other)==0 // note: datatype doesn't matter
}
operator fun compareTo(other: Value): Int {
if(stringvalue!=null && other.stringvalue!=null)
return stringvalue.compareTo(other.stringvalue)
return numericValue().toDouble().compareTo(other.numericValue().toDouble())
}
@ -446,21 +463,21 @@ class Value(val type: DataType, numericvalue: Number?, val stringvalue: String?=
fun bitand(other: Value): Value {
val v1 = integerValue()
val v2 = other.integerValue()
val result = v1.and(v2)
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)
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)
val result = v1 xor v2
return Value(type, result)
}
@ -470,16 +487,16 @@ class Value(val type: DataType, numericvalue: Number?, val stringvalue: String?=
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")
DataType.BYTE -> Value(DataType.BYTE, byteval!!.toInt().inv() and 255)
DataType.WORD -> Value(DataType.WORD, wordval!!.inv() and 65535)
else -> throw VmExecutionException("inv 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.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")
}
@ -487,8 +504,8 @@ class Value(val type: DataType, numericvalue: Number?, val stringvalue: String?=
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.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")
}
@ -496,7 +513,7 @@ class Value(val type: DataType, numericvalue: Number?, val stringvalue: String?=
fun lsb(): Value {
return when(type) {
DataType.BYTE -> Value(DataType.BYTE, byteval!!.toInt() and 255)
DataType.BYTE -> Value(DataType.BYTE, byteval!!)
DataType.WORD -> Value(DataType.WORD, wordval!! and 255)
else -> throw VmExecutionException("not can only work on byte/word")
}
@ -504,7 +521,7 @@ class Value(val type: DataType, numericvalue: Number?, val stringvalue: String?=
fun msb(): Value {
return when(type) {
DataType.BYTE -> Value(DataType.BYTE, byteval!!.toInt() ushr 8 and 255)
DataType.BYTE -> Value(DataType.BYTE, 0)
DataType.WORD -> Value(DataType.WORD, wordval!! ushr 8 and 255)
else -> throw VmExecutionException("not can only work on byte/word")
}
@ -543,13 +560,13 @@ class LabelInstr(val name: String) : Instruction(opcode = Opcode.NOP) {
}
}
private class VmExecutionException(msg: String?) : Exception(msg)
class VmExecutionException(msg: String?) : Exception(msg)
private class VmTerminationException(msg: String?) : Exception(msg)
class VmTerminationException(msg: String?) : Exception(msg)
private class VmBreakpointException : Exception("breakpoint")
class VmBreakpointException : Exception("breakpoint")
private class MyStack<T> : Stack<T>() {
class MyStack<T> : Stack<T>() {
fun peek(amount: Int) : List<T> {
return this.toList().subList(max(0, size-amount), size)
}
@ -822,48 +839,56 @@ class Program (val name: String,
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 P_carry: Boolean = false
private var P_irqd: Boolean = false
val mem = Memory()
val evalstack = MyStack<Value>() // evaluation stack
val callstack = MyStack<Instruction>() // subroutine call stack
var sourceLine = "" // meta info about current line in source file
private set
var P_carry: Boolean = false
private set
var P_irqd: Boolean = false
private set
var variables = mutableMapOf<String, Value>() // all variables (set of all vars used by all blocks/subroutines) key = their fully scoped name
private set
private var program = listOf<Instruction>()
private var traceOutput = if(traceOutputFile!=null) PrintStream(File(traceOutputFile), "utf-8") else null
private lateinit var currentIns: Instruction
private lateinit var canvas: BitmapScreenPanel
private var canvas: BitmapScreenPanel? = null
private val rnd = Random()
private var sourceLine = ""
fun load(program: Program, canvas: BitmapScreenPanel) {
fun load(program: Program, canvas: BitmapScreenPanel?) {
this.program = program.program
this.canvas = canvas
this.variables = program.variables.toMutableMap()
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"))
variables.contains("X") ||
variables.contains("Y") ||
variables.contains("XY") ||
variables.contains("AX") ||
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)
variables["A"] = Value(DataType.BYTE, 0)
variables["X"] = Value(DataType.BYTE, 0)
variables["Y"] = Value(DataType.BYTE, 0)
variables["AX"] = Value(DataType.WORD, 0)
variables["AY"] = Value(DataType.WORD, 0)
variables["XY"] = Value(DataType.WORD, 0)
initMemory(program.memory)
evalstack.clear()
callstack.clear()
P_carry = false
P_irqd = false
sourceLine = ""
currentIns = this.program[0]
}
fun step() {
fun step(instructionCount: Int = 10000) {
// step is invoked every 1/100 sec
// we execute 10k instructions in one go so we end up doing 1 million vm instructions per second
val instructionsPerStep = 10000
val start = System.currentTimeMillis()
for(i:Int in 0..instructionsPerStep) {
for(i:Int in 1..instructionCount) {
try {
currentIns = dispatch(currentIns)
@ -890,6 +915,7 @@ class StackVm(val traceOutputFile: String?) {
}
private fun initMemory(memory: Map<Int, List<Value>>) {
mem.clear()
for (meminit in memory) {
var address = meminit.key
for (value in meminit.value) {
@ -936,14 +962,19 @@ class StackVm(val traceOutputFile: String?) {
Opcode.DUP -> evalstack.push(evalstack.peek())
Opcode.ARRAY -> {
val amount = ins.arg!!.integerValue()
if(amount<=0)
throw VmExecutionException("array size must be > 0")
val array = mutableListOf<Int>()
var arrayDt = DataType.ARRAY
for (i in 1..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())
if(value.type==DataType.WORD)
arrayDt = DataType.ARRAY_W
array.add(0, value.integerValue())
}
evalstack.push(Value(DataType.ARRAY, null, arrayvalue = array.toIntArray()))
evalstack.push(Value(arrayDt, null, arrayvalue = array.toIntArray()))
}
Opcode.DISCARD -> evalstack.pop()
Opcode.SWAP -> {
@ -1080,17 +1111,17 @@ class StackVm(val traceOutputFile: String?) {
// 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())
canvas?.setPixel(x.integerValue(), y.integerValue(), color.integerValue())
}
Syscall.GFX_CLEARSCR -> {
val color = evalstack.pop()
canvas.clearScreen(color.integerValue())
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())
canvas?.writeText(x.integerValue(), y.integerValue(), text.stringvalue!!, color.integerValue())
}
Syscall.FUNC_RND -> evalstack.push(Value(DataType.BYTE, rnd.nextInt() and 255))
Syscall.FUNC_RNDW -> evalstack.push(Value(DataType.WORD, rnd.nextInt() and 65535))
@ -1108,8 +1139,6 @@ class StackVm(val traceOutputFile: String?) {
Syscall.FUNC_SIN -> evalstack.push(Value(DataType.FLOAT, sin(evalstack.pop().numericValue().toDouble())))
Syscall.FUNC_COS -> evalstack.push(Value(DataType.FLOAT, cos(evalstack.pop().numericValue().toDouble())))
Syscall.FUNC_ROUND -> evalstack.push(Value(DataType.WORD, evalstack.pop().numericValue().toDouble().roundToInt()))
Syscall.FUNC_P_CARRY -> P_carry = evalstack.pop().asBooleanValue
Syscall.FUNC_P_IRQD -> P_irqd = evalstack.pop().asBooleanValue
Syscall.FUNC_ABS -> {
val value = evalstack.pop()
val absValue=
@ -1131,7 +1160,6 @@ class StackVm(val traceOutputFile: String?) {
Syscall.FUNC_SQRT -> evalstack.push(Value(DataType.FLOAT, sqrt(evalstack.pop().numericValue().toDouble())))
Syscall.FUNC_RAD -> evalstack.push(Value(DataType.FLOAT, Math.toRadians(evalstack.pop().numericValue().toDouble())))
Syscall.FUNC_DEG -> evalstack.push(Value(DataType.FLOAT, Math.toDegrees(evalstack.pop().numericValue().toDouble())))
Syscall.FUNC_FLT -> evalstack.push(Value(DataType.FLOAT, evalstack.pop().numericValue().toDouble()))
Syscall.FUNC_FLOOR -> {
val value = evalstack.pop()
val result =
@ -1196,6 +1224,8 @@ class StackVm(val traceOutputFile: String?) {
Opcode.SEC -> P_carry = true
Opcode.CLC -> P_carry = false
Opcode.SEI -> P_irqd = true
Opcode.CLI -> P_irqd = false
Opcode.TERMINATE -> throw VmTerminationException("terminate instruction")
Opcode.BREAKPOINT -> throw VmBreakpointException()

File diff suppressed because it is too large Load Diff