vm: add many builtin functions

This commit is contained in:
Irmen de Jong 2022-04-11 22:39:33 +02:00
parent a2c7273801
commit 51bf33040a
6 changed files with 174 additions and 82 deletions

View File

@ -44,5 +44,5 @@ class VirtualMachineDefinition: IMachineDefinition {
} }
interface IVirtualMachineRunner { interface IVirtualMachineRunner {
fun runProgram(program: String, throttle: Boolean) fun runProgram(source: String, throttle: Boolean)
} }

View File

@ -2,6 +2,7 @@ package prog8.codegen.virtual
import prog8.code.StStaticVariable import prog8.code.StStaticVariable
import prog8.code.ast.* import prog8.code.ast.*
import prog8.code.core.AssemblyError
import prog8.code.core.DataType import prog8.code.core.DataType
import prog8.vm.Opcode import prog8.vm.Opcode
import prog8.vm.Syscall import prog8.vm.Syscall
@ -11,6 +12,54 @@ internal class BuiltinFuncGen(private val codeGen: CodeGen, private val exprGen:
fun translate(call: PtBuiltinFunctionCall, resultRegister: Int): VmCodeChunk { fun translate(call: PtBuiltinFunctionCall, resultRegister: Int): VmCodeChunk {
return when(call.name) { return when(call.name) {
"cmp" -> TODO()
"max" -> TODO()
"min" -> TODO()
"sum" -> TODO()
"abs" -> TODO()
"sgn" -> funcSgn(call, resultRegister)
"sin" -> TODO("floats not yet implemented")
"sin8" -> TODO()
"sin16" -> TODO()
"sin16u" -> TODO()
"sinr8" -> TODO()
"sinr8u" -> TODO()
"sinr16" -> TODO()
"sinr16u" -> TODO()
"cos" -> TODO("floats not yet implemented")
"cos8" -> TODO()
"cos16" -> TODO()
"cos16u" -> TODO()
"cosr8" -> TODO()
"cosr8u" -> TODO()
"cosr16" -> TODO()
"cosr16u" -> TODO()
"tan" -> TODO("floats not yet implemented")
"atan" -> TODO("floats not yet implemented")
"ln" -> TODO("floats not yet implemented")
"log2" -> TODO("floats not yet implemented")
"sqrt16" -> funcSqrt16(call, resultRegister)
"sqrt" -> TODO("floats not yet implemented")
"rad" -> TODO("floats not yet implemented")
"deg" -> TODO("floats not yet implemented")
"round" -> TODO("floats not yet implemented")
"floor" -> TODO("floats not yet implemented")
"ceil" -> TODO("floats not yet implemented")
"any" -> TODO()
"all" -> TODO()
"pop" -> funcPop(call)
"popw" -> funcPopw(call)
"push" -> funcPush(call)
"pushw" -> funcPushw(call)
"rsave",
"rsavex",
"rrestore",
"rrestorex" -> VmCodeChunk() // vm doesn't have registers to save/restore
"rnd" -> funcRnd(resultRegister)
"rndw" -> funcRndw(resultRegister)
"rndf" -> TODO("floats not yet implemented")
"callfar" -> throw AssemblyError("callfar() is for cx16 target only")
"callrom" -> throw AssemblyError("callrom() is for cx16 target only")
"syscall" -> funcSyscall(call) "syscall" -> funcSyscall(call)
"syscall1" -> funcSyscall1(call) "syscall1" -> funcSyscall1(call)
"syscall2" -> funcSyscall2(call) "syscall2" -> funcSyscall2(call)
@ -18,7 +67,6 @@ internal class BuiltinFuncGen(private val codeGen: CodeGen, private val exprGen:
"msb" -> funcMsb(call, resultRegister) "msb" -> funcMsb(call, resultRegister)
"lsb" -> funcLsb(call, resultRegister) "lsb" -> funcLsb(call, resultRegister)
"memory" -> funcMemory(call, resultRegister) "memory" -> funcMemory(call, resultRegister)
"rnd" -> funcRnd(resultRegister)
"peek" -> funcPeek(call, resultRegister) "peek" -> funcPeek(call, resultRegister)
"peekw" -> funcPeekW(call, resultRegister) "peekw" -> funcPeekW(call, resultRegister)
"poke" -> funcPoke(call) "poke" -> funcPoke(call)
@ -34,28 +82,50 @@ internal class BuiltinFuncGen(private val codeGen: CodeGen, private val exprGen:
"ror" -> funcRolRor2(Opcode.ROXR, call, resultRegister) "ror" -> funcRolRor2(Opcode.ROXR, call, resultRegister)
"rol2" -> funcRolRor2(Opcode.ROL, call, resultRegister) "rol2" -> funcRolRor2(Opcode.ROL, call, resultRegister)
"ror2" -> funcRolRor2(Opcode.ROR, call, resultRegister) "ror2" -> funcRolRor2(Opcode.ROR, call, resultRegister)
else -> { else -> TODO("builtinfunc ${call.name}")
TODO("builtinfunc ${call.name}")
// code += VmCodeInstruction(Opcode.NOP))
// for (arg in call.args) {
// code += translateExpression(arg, resultRegister)
// code += when(arg.type) {
// in ByteDatatypes -> VmCodeInstruction(Opcode.PUSH, VmDataType.BYTE, reg1=resultRegister))
// in WordDatatypes -> VmCodeInstruction(Opcode.PUSH, VmDataType.WORD, reg1=resultRegister))
// else -> throw AssemblyError("weird arg dt")
// }
// }
// code += VmCodeInstruction(Opcode.CALL), labelArg = listOf("_prog8_builtin", call.name))
// for (arg in call.args) {
// code += when(arg.type) {
// in ByteDatatypes -> VmCodeInstruction(Opcode.POP, VmDataType.BYTE, reg1=resultRegister))
// in WordDatatypes -> VmCodeInstruction(Opcode.POP, VmDataType.WORD, reg1=resultRegister))
// else -> throw AssemblyError("weird arg dt")
// }
// }
// code += VmCodeInstruction(Opcode.NOP))
} }
} }
private fun funcSgn(call: PtBuiltinFunctionCall, resultRegister: Int): VmCodeChunk {
val code = VmCodeChunk()
code += exprGen.translateExpression(call.args.single(), 0)
code += VmCodeInstruction(Opcode.SGN, codeGen.vmType(call.type), reg1=resultRegister, reg2=0)
return code
}
private fun funcSqrt16(call: PtBuiltinFunctionCall, resultRegister: Int): VmCodeChunk {
val code = VmCodeChunk()
code += exprGen.translateExpression(call.args.single(), 0)
code += VmCodeInstruction(Opcode.SQRT, VmDataType.WORD, reg1=resultRegister, reg2=0)
return code
}
private fun funcPop(call: PtBuiltinFunctionCall): VmCodeChunk {
val code = VmCodeChunk()
code += VmCodeInstruction(Opcode.POP, VmDataType.BYTE, reg1=0)
code += assignRegisterTo(call.args.single(), 0)
return code
}
private fun funcPopw(call: PtBuiltinFunctionCall): VmCodeChunk {
val code = VmCodeChunk()
code += VmCodeInstruction(Opcode.POP, VmDataType.WORD, reg1=0)
code += assignRegisterTo(call.args.single(), 0)
return code
}
private fun funcPush(call: PtBuiltinFunctionCall): VmCodeChunk {
val code = VmCodeChunk()
code += exprGen.translateExpression(call.args.single(), 0)
code += VmCodeInstruction(Opcode.PUSH, VmDataType.BYTE, reg1=0)
return code
}
private fun funcPushw(call: PtBuiltinFunctionCall): VmCodeChunk {
val code = VmCodeChunk()
code += exprGen.translateExpression(call.args.single(), 0)
code += VmCodeInstruction(Opcode.PUSH, VmDataType.WORD, reg1=0)
return code
} }
private fun funcSwap(call: PtBuiltinFunctionCall): VmCodeChunk { private fun funcSwap(call: PtBuiltinFunctionCall): VmCodeChunk {
@ -202,6 +272,14 @@ internal class BuiltinFuncGen(private val codeGen: CodeGen, private val exprGen:
return code return code
} }
private fun funcRndw(resultRegister: Int): VmCodeChunk {
val code = VmCodeChunk()
code += VmCodeInstruction(Opcode.SYSCALL, value= Syscall.RNDW.ordinal)
if(resultRegister!=0)
code += VmCodeInstruction(Opcode.LOADR, VmDataType.WORD, reg1=resultRegister, reg2=0)
return code
}
private fun funcMemory(call: PtBuiltinFunctionCall, resultRegister: Int): VmCodeChunk { private fun funcMemory(call: PtBuiltinFunctionCall, resultRegister: Int): VmCodeChunk {
val name = (call.args[0] as PtString).value val name = (call.args[0] as PtString).value
val size = (call.args[1] as PtNumber).number.toUInt() val size = (call.args[1] as PtNumber).number.toUInt()

View File

@ -7,11 +7,14 @@
main { main {
sub start() { sub start() {
pokew($1000, $ea31) word zz = 0
poke($1001, $44) txt.print_b(sgn(zz))
txt.print_uwhex(peekw($1000), true)
txt.nl() txt.nl()
txt.print_ubhex(peek($1001), true) zz = -100
txt.print_b(sgn(zz))
txt.nl()
zz = 9999
txt.print_b(sgn(zz))
txt.nl() txt.nl()
; uword other = $fe4a ; uword other = $fe4a

View File

@ -6,7 +6,7 @@ Virtual machine:
65536 virtual registers, 16 bits wide, can also be used as 8 bits. r0-r65535 65536 virtual registers, 16 bits wide, can also be used as 8 bits. r0-r65535
65536 bytes of memory. Thus memory pointers (addresses) are limited to 16 bits. 65536 bytes of memory. Thus memory pointers (addresses) are limited to 16 bits.
Value stack, max 128 entries. Value stack, max 128 entries of 1 byte each.
Status registers: Carry. Status registers: Carry.
@ -107,6 +107,8 @@ sub reg1, reg2, reg3 - reg1 = reg2-reg3 (unsigned + signe
mul reg1, reg2, reg3 - unsigned multiply reg1=reg2*reg3 note: byte*byte->byte, no type extension to word! 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 div reg1, reg2, reg3 - unsigned division reg1=reg2/reg3 note: division by zero yields max signed int $ff/$ffff
mod reg1, reg2, reg3 - remainder (modulo) of unsigned division reg1=reg2%reg3 note: division by zero yields max signed int $ff/$ffff mod reg1, reg2, reg3 - remainder (modulo) of unsigned division reg1=reg2%reg3 note: division by zero yields max signed int $ff/$ffff
sqrt reg1, reg2 - reg1 is the square root of reg2 (for .w and .b both , the result is a byte)
sgn reg1, reg2 - reg1 is the sign of reg2 (0, 1 or -1)
NOTE: because mul/div are constrained (truncated) to remain in 8 or 16 bits, there is NO NEED for separate signed/unsigned mul and div instructions. The result is identical. NOTE: because mul/div are constrained (truncated) to remain in 8 or 16 bits, there is NO NEED for separate signed/unsigned mul and div instructions. The result is identical.
@ -203,6 +205,8 @@ enum class Opcode {
MUL, MUL,
DIV, DIV,
MOD, MOD,
SQRT,
SGN,
EXT, EXT,
EXTS, EXTS,
@ -227,8 +231,6 @@ enum class Opcode {
MSIG, MSIG,
SWAPREG, SWAPREG,
CONCAT, CONCAT,
COPY,
COPYZ,
BREAKPOINT BREAKPOINT
} }
@ -372,6 +374,8 @@ val instructionFormats = mutableMapOf(
Opcode.MUL to InstructionFormat(BW, true, true, true, false), Opcode.MUL to InstructionFormat(BW, true, true, true, false),
Opcode.DIV to InstructionFormat(BW, true, true, true, false), Opcode.DIV to InstructionFormat(BW, true, true, true, false),
Opcode.MOD to InstructionFormat(BW, true, true, true, false), Opcode.MOD to InstructionFormat(BW, true, true, true, false),
Opcode.SQRT to InstructionFormat(BW, true, true, false, false),
Opcode.SGN to InstructionFormat(BW, true, true, false, false),
Opcode.EXT to InstructionFormat(BW, true, false, false, false), Opcode.EXT to InstructionFormat(BW, true, false, false, false),
Opcode.EXTS to InstructionFormat(BW, true, false, false, false), Opcode.EXTS to InstructionFormat(BW, true, false, false, false),
@ -389,8 +393,6 @@ val instructionFormats = mutableMapOf(
Opcode.ROL to InstructionFormat(BW, true, false, false, false), Opcode.ROL to InstructionFormat(BW, true, false, false, false),
Opcode.ROXL to InstructionFormat(BW, true, false, false, false), Opcode.ROXL to InstructionFormat(BW, true, false, false, false),
Opcode.COPY to InstructionFormat(NN, true, true, false, true ),
Opcode.COPYZ to InstructionFormat(NN, true, true, false, false),
Opcode.MSIG to InstructionFormat(BW, true, true, false, false), Opcode.MSIG to InstructionFormat(BW, true, true, false, false),
Opcode.PUSH to InstructionFormat(BW, true, false, false, false), Opcode.PUSH to InstructionFormat(BW, true, false, false, false),
Opcode.POP to InstructionFormat(BW, true, false, false, false), Opcode.POP to InstructionFormat(BW, true, false, false, false),

View File

@ -18,16 +18,17 @@ SYSCALLS:
9 = gfx_clear ; clear graphics window with shade in r0.b 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 10 = gfx_plot ; plot pixel in graphics window, r0.w/r1.w contain X and Y coordinates, r2.b contains brightness
11 = rnd ; random BYTE 11 = rnd ; random BYTE
12 = wait ; wait certain amount of jiffies (1/60 sec) 12 = rndw ; random WORD
13 = waitvsync ; wait on vsync 13 = wait ; wait certain amount of jiffies (1/60 sec)
14 = sin8u 14 = waitvsync ; wait on vsync
15 = cos8u 15 = sin8u
16 = sort_ubyte array 16 = cos8u
17 = sort_byte array 17 = sort_ubyte array
18 = sort_uword array 18 = sort_byte array
19 = sort_word array 19 = sort_uword array
20 = reverse_bytes array 20 = sort_word array
21 = reverse_words array 21 = reverse_bytes array
22 = reverse_words array
*/ */
enum class Syscall { enum class Syscall {
@ -43,6 +44,7 @@ enum class Syscall {
GFX_CLEAR, GFX_CLEAR,
GFX_PLOT, GFX_PLOT,
RND, RND,
RNDW,
WAIT, WAIT,
WAITVSYNC, WAITVSYNC,
SIN8U, SIN8U,
@ -100,7 +102,10 @@ object SysCalls {
Syscall.GFX_CLEAR -> vm.gfx_clear() Syscall.GFX_CLEAR -> vm.gfx_clear()
Syscall.GFX_PLOT -> vm.gfx_plot() Syscall.GFX_PLOT -> vm.gfx_plot()
Syscall.RND -> { Syscall.RND -> {
vm.registers.setUB(0, (Random.nextInt() ushr 3).toUByte()) vm.registers.setUB(0, Random.nextInt().toUByte())
}
Syscall.RNDW -> {
vm.registers.setUW(0, Random.nextInt().toUShort())
} }
Syscall.WAIT -> { Syscall.WAIT -> {
val millis = vm.registers.getUW(0).toLong() * 1000/60 val millis = vm.registers.getUW(0).toLong() * 1000/60

View File

@ -4,6 +4,8 @@ import prog8.code.target.virtual.IVirtualMachineRunner
import java.awt.Toolkit import java.awt.Toolkit
import java.util.* import java.util.*
import kotlin.math.roundToInt import kotlin.math.roundToInt
import kotlin.math.sign
import kotlin.math.sqrt
class ProgramExitException(val status: Int): Exception() class ProgramExitException(val status: Int): Exception()
@ -17,7 +19,7 @@ class VirtualMachine(val memory: Memory, program: List<Instruction>) {
val registers = Registers() val registers = Registers()
val program: Array<Instruction> = program.toTypedArray() val program: Array<Instruction> = program.toTypedArray()
val callStack = Stack<Int>() val callStack = Stack<Int>()
val valueStack = Stack<Int>() // max 128 entries val valueStack = Stack<UByte>() // max 128 entries
var pc = 0 var pc = 0
var stepCount = 0 var stepCount = 0
var statusCarry = false var statusCarry = false
@ -135,6 +137,8 @@ class VirtualMachine(val memory: Memory, program: List<Instruction>) {
Opcode.MUL -> InsMUL(ins) Opcode.MUL -> InsMUL(ins)
Opcode.DIV -> InsDIV(ins) Opcode.DIV -> InsDIV(ins)
Opcode.MOD -> InsMOD(ins) Opcode.MOD -> InsMOD(ins)
Opcode.SQRT -> InsSQRT(ins)
Opcode.SGN -> InsSGN(ins)
Opcode.EXT -> InsEXT(ins) Opcode.EXT -> InsEXT(ins)
Opcode.EXTS -> InsEXTS(ins) Opcode.EXTS -> InsEXTS(ins)
Opcode.AND -> InsAND(ins) Opcode.AND -> InsAND(ins)
@ -154,8 +158,6 @@ class VirtualMachine(val memory: Memory, program: List<Instruction>) {
Opcode.CONCAT -> InsCONCAT(ins) Opcode.CONCAT -> InsCONCAT(ins)
Opcode.PUSH -> InsPUSH(ins) Opcode.PUSH -> InsPUSH(ins)
Opcode.POP -> InsPOP(ins) Opcode.POP -> InsPOP(ins)
Opcode.COPY -> InsCOPY(ins)
Opcode.COPYZ -> InsCOPYZ(ins)
Opcode.BREAKPOINT -> InsBREAKPOINT() Opcode.BREAKPOINT -> InsBREAKPOINT()
else -> throw IllegalArgumentException("invalid opcode ${ins.opcode}") else -> throw IllegalArgumentException("invalid opcode ${ins.opcode}")
} }
@ -172,17 +174,32 @@ class VirtualMachine(val memory: Memory, program: List<Instruction>) {
if(valueStack.size>=128) if(valueStack.size>=128)
throw StackOverflowError("valuestack limit 128 exceeded") throw StackOverflowError("valuestack limit 128 exceeded")
val value = when(i.type!!) { when(i.type!!) {
VmDataType.BYTE -> registers.getUB(i.reg1!!).toInt() VmDataType.BYTE -> {
VmDataType.WORD -> registers.getUW(i.reg1!!).toInt() val value = registers.getUB(i.reg1!!)
}
valueStack.push(value) valueStack.push(value)
}
VmDataType.WORD -> {
val value = registers.getUW(i.reg1!!)
valueStack.push((value and 255u).toUByte())
valueStack.push((value.toInt() ushr 8).toUByte())
}
}
pc++ pc++
} }
private fun InsPOP(i: Instruction) { private fun InsPOP(i: Instruction) {
val value = valueStack.pop() val value = when(i.type!!) {
setResultReg(i.reg1!!, value, i.type!!) VmDataType.BYTE -> {
valueStack.pop().toInt()
}
VmDataType.WORD -> {
val msb = valueStack.pop()
val lsb = valueStack.pop()
(msb.toInt() shl 8) + lsb.toInt()
}
}
setResultReg(i.reg1!!, value, i.type)
pc++ pc++
} }
@ -597,6 +614,22 @@ class VirtualMachine(val memory: Memory, program: List<Instruction>) {
pc++ pc++
} }
private fun InsSQRT(i: Instruction) {
when(i.type!!) {
VmDataType.BYTE -> registers.setUB(i.reg1!!, sqrt(registers.getUB(i.reg2!!).toDouble()).toInt().toUByte())
VmDataType.WORD -> registers.setUB(i.reg1!!, sqrt(registers.getUW(i.reg2!!).toDouble()).toInt().toUByte())
}
pc++
}
private fun InsSGN(i: Instruction) {
when(i.type!!) {
VmDataType.BYTE -> registers.setSB(i.reg1!!, registers.getSB(i.reg2!!).toInt().sign.toByte())
VmDataType.WORD -> registers.setSW(i.reg1!!, registers.getSW(i.reg2!!).toInt().sign.toShort())
}
pc++
}
private fun arithByte(operator: String, reg1: Int, reg2: Int, reg3: Int?, value: UByte?) { private fun arithByte(operator: String, reg1: Int, reg2: Int, reg3: Int?, value: UByte?) {
val left = registers.getUB(reg2) val left = registers.getUB(reg2)
val right = value ?: registers.getUB(reg3!!) val right = value ?: registers.getUB(reg3!!)
@ -851,35 +884,6 @@ class VirtualMachine(val memory: Memory, program: List<Instruction>) {
pc++ 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.getUW(reg1).toInt()
var to = registers.getUW(reg2).toInt()
if(untilzero) {
while(true) {
val char = memory.getUB(from)
memory.setUB(to, char)
if(char.toInt()==0)
break
from++
to++
}
} else {
var len = length!!
while(len>0) {
val char = memory.getUB(from)
memory.setUB(to, char)
from++
to++
len--
}
}
pc++
}
private fun getBranchOperands(i: Instruction): Pair<Int, Int> { private fun getBranchOperands(i: Instruction): Pair<Int, Int> {
return when(i.type) { return when(i.type) {
VmDataType.BYTE -> Pair(registers.getSB(i.reg1!!).toInt(), registers.getSB(i.reg2!!).toInt()) VmDataType.BYTE -> Pair(registers.getSB(i.reg1!!).toInt(), registers.getSB(i.reg2!!).toInt())
@ -957,7 +961,7 @@ class VirtualMachine(val memory: Memory, program: List<Instruction>) {
} }
} }
// probably called via reflection
class VmRunner(): IVirtualMachineRunner { class VmRunner(): IVirtualMachineRunner {
override fun runProgram(source: String, throttle: Boolean) { override fun runProgram(source: String, throttle: Boolean) {
val (memsrc, programsrc) = source.split("------PROGRAM------".toRegex(), 2) val (memsrc, programsrc) = source.split("------PROGRAM------".toRegex(), 2)