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 {
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.ast.*
import prog8.code.core.AssemblyError
import prog8.code.core.DataType
import prog8.vm.Opcode
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 {
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)
"syscall1" -> funcSyscall1(call)
"syscall2" -> funcSyscall2(call)
@ -18,7 +67,6 @@ internal class BuiltinFuncGen(private val codeGen: CodeGen, private val exprGen:
"msb" -> funcMsb(call, resultRegister)
"lsb" -> funcLsb(call, resultRegister)
"memory" -> funcMemory(call, resultRegister)
"rnd" -> funcRnd(resultRegister)
"peek" -> funcPeek(call, resultRegister)
"peekw" -> funcPeekW(call, resultRegister)
"poke" -> funcPoke(call)
@ -34,30 +82,52 @@ internal class BuiltinFuncGen(private val codeGen: CodeGen, private val exprGen:
"ror" -> funcRolRor2(Opcode.ROXR, call, resultRegister)
"rol2" -> funcRolRor2(Opcode.ROL, call, resultRegister)
"ror2" -> funcRolRor2(Opcode.ROR, call, resultRegister)
else -> {
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))
}
else -> TODO("builtinfunc ${call.name}")
}
}
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 {
val left = call.args[0]
val right = call.args[1]
@ -202,6 +272,14 @@ internal class BuiltinFuncGen(private val codeGen: CodeGen, private val exprGen:
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 {
val name = (call.args[0] as PtString).value
val size = (call.args[1] as PtNumber).number.toUInt()

View File

@ -7,11 +7,14 @@
main {
sub start() {
pokew($1000, $ea31)
poke($1001, $44)
txt.print_uwhex(peekw($1000), true)
word zz = 0
txt.print_b(sgn(zz))
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()
; 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 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.
@ -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!
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
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.
@ -203,6 +205,8 @@ enum class Opcode {
MUL,
DIV,
MOD,
SQRT,
SGN,
EXT,
EXTS,
@ -227,8 +231,6 @@ enum class Opcode {
MSIG,
SWAPREG,
CONCAT,
COPY,
COPYZ,
BREAKPOINT
}
@ -372,6 +374,8 @@ val instructionFormats = mutableMapOf(
Opcode.MUL 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.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.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.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.PUSH 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
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
12 = wait ; wait certain amount of jiffies (1/60 sec)
13 = waitvsync ; wait on vsync
14 = sin8u
15 = cos8u
16 = sort_ubyte array
17 = sort_byte array
18 = sort_uword array
19 = sort_word array
20 = reverse_bytes array
21 = reverse_words array
12 = rndw ; random WORD
13 = wait ; wait certain amount of jiffies (1/60 sec)
14 = waitvsync ; wait on vsync
15 = sin8u
16 = cos8u
17 = sort_ubyte array
18 = sort_byte array
19 = sort_uword array
20 = sort_word array
21 = reverse_bytes array
22 = reverse_words array
*/
enum class Syscall {
@ -43,6 +44,7 @@ enum class Syscall {
GFX_CLEAR,
GFX_PLOT,
RND,
RNDW,
WAIT,
WAITVSYNC,
SIN8U,
@ -100,7 +102,10 @@ object SysCalls {
Syscall.GFX_CLEAR -> vm.gfx_clear()
Syscall.GFX_PLOT -> vm.gfx_plot()
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 -> {
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.util.*
import kotlin.math.roundToInt
import kotlin.math.sign
import kotlin.math.sqrt
class ProgramExitException(val status: Int): Exception()
@ -17,7 +19,7 @@ class VirtualMachine(val memory: Memory, program: List<Instruction>) {
val registers = Registers()
val program: Array<Instruction> = program.toTypedArray()
val callStack = Stack<Int>()
val valueStack = Stack<Int>() // max 128 entries
val valueStack = Stack<UByte>() // max 128 entries
var pc = 0
var stepCount = 0
var statusCarry = false
@ -135,6 +137,8 @@ class VirtualMachine(val memory: Memory, program: List<Instruction>) {
Opcode.MUL -> InsMUL(ins)
Opcode.DIV -> InsDIV(ins)
Opcode.MOD -> InsMOD(ins)
Opcode.SQRT -> InsSQRT(ins)
Opcode.SGN -> InsSGN(ins)
Opcode.EXT -> InsEXT(ins)
Opcode.EXTS -> InsEXTS(ins)
Opcode.AND -> InsAND(ins)
@ -154,8 +158,6 @@ class VirtualMachine(val memory: Memory, program: List<Instruction>) {
Opcode.CONCAT -> InsCONCAT(ins)
Opcode.PUSH -> InsPUSH(ins)
Opcode.POP -> InsPOP(ins)
Opcode.COPY -> InsCOPY(ins)
Opcode.COPYZ -> InsCOPYZ(ins)
Opcode.BREAKPOINT -> InsBREAKPOINT()
else -> throw IllegalArgumentException("invalid opcode ${ins.opcode}")
}
@ -172,17 +174,32 @@ class VirtualMachine(val memory: Memory, program: List<Instruction>) {
if(valueStack.size>=128)
throw StackOverflowError("valuestack limit 128 exceeded")
val value = when(i.type!!) {
VmDataType.BYTE -> registers.getUB(i.reg1!!).toInt()
VmDataType.WORD -> registers.getUW(i.reg1!!).toInt()
when(i.type!!) {
VmDataType.BYTE -> {
val value = registers.getUB(i.reg1!!)
valueStack.push(value)
}
VmDataType.WORD -> {
val value = registers.getUW(i.reg1!!)
valueStack.push((value and 255u).toUByte())
valueStack.push((value.toInt() ushr 8).toUByte())
}
}
valueStack.push(value)
pc++
}
private fun InsPOP(i: Instruction) {
val value = valueStack.pop()
setResultReg(i.reg1!!, value, i.type!!)
val value = when(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++
}
@ -597,6 +614,22 @@ class VirtualMachine(val memory: Memory, program: List<Instruction>) {
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?) {
val left = registers.getUB(reg2)
val right = value ?: registers.getUB(reg3!!)
@ -851,35 +884,6 @@ class VirtualMachine(val memory: Memory, program: List<Instruction>) {
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> {
return when(i.type) {
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 {
override fun runProgram(source: String, throttle: Boolean) {
val (memsrc, programsrc) = source.split("------PROGRAM------".toRegex(), 2)