diff --git a/codeGenVirtual/src/prog8/codegen/virtual/BuiltinFuncGen.kt b/codeGenVirtual/src/prog8/codegen/virtual/BuiltinFuncGen.kt index c8e071569..03e3e3305 100644 --- a/codeGenVirtual/src/prog8/codegen/virtual/BuiltinFuncGen.kt +++ b/codeGenVirtual/src/prog8/codegen/virtual/BuiltinFuncGen.kt @@ -1,10 +1,7 @@ package prog8.codegen.virtual import prog8.code.StStaticVariable -import prog8.code.ast.PtBuiltinFunctionCall -import prog8.code.ast.PtIdentifier -import prog8.code.ast.PtNumber -import prog8.code.ast.PtString +import prog8.code.ast.* import prog8.code.core.DataType import prog8.vm.Opcode import prog8.vm.Syscall @@ -154,6 +151,10 @@ internal class BuiltinFuncGen(private val codeGen: CodeGen, private val exprGen: code += VmCodeInstruction(Opcode.LOAD, VmDataType.BYTE, reg1=1, value=array.length) code += VmCodeInstruction(Opcode.SYSCALL, value=sortSyscall.ordinal) } + "rol" -> RolRor2(Opcode.ROXL, call, resultRegister, code) + "ror" -> RolRor2(Opcode.ROXR, call, resultRegister, code) + "rol2" -> RolRor2(Opcode.ROL, call, resultRegister, code) + "ror2" -> RolRor2(Opcode.ROR, call, resultRegister, code) else -> { TODO("builtinfunc ${call.name}") // code += VmCodeInstruction(Opcode.NOP)) @@ -179,4 +180,17 @@ internal class BuiltinFuncGen(private val codeGen: CodeGen, private val exprGen: return code } + private fun RolRor2(opcode: Opcode, call: PtBuiltinFunctionCall, resultRegister: Int, code: VmCodeChunk) { + // bit rotate left without carry, in-place + val vmDt = codeGen.vmType(call.args[0].type) + code += exprGen.translateExpression(call.args[0], resultRegister) + code += VmCodeInstruction(opcode, vmDt, reg1=resultRegister) + val assignment = PtAssignment(call.position) + val target = PtAssignTarget(call.position) + target.children.add(call.args[0]) + assignment.children.add(target) + assignment.children.add(PtIdentifier(listOf(":vmreg-$resultRegister"), listOf(":vmreg-$resultRegister"), call.args[0].type, call.position)) + code += codeGen.translateNode(assignment) + } + } diff --git a/codeGenVirtual/src/prog8/codegen/virtual/CodeGen.kt b/codeGenVirtual/src/prog8/codegen/virtual/CodeGen.kt index ca30f18e0..9003409d1 100644 --- a/codeGenVirtual/src/prog8/codegen/virtual/CodeGen.kt +++ b/codeGenVirtual/src/prog8/codegen/virtual/CodeGen.kt @@ -57,7 +57,7 @@ class CodeGen(internal val program: PtProgram, } - private fun translateNode(node: PtNode): VmCodeChunk { + internal fun translateNode(node: PtNode): VmCodeChunk { val code = when(node) { is PtBlock -> translate(node) is PtSub -> translate(node) @@ -80,6 +80,7 @@ class CodeGen(internal val program: PtProgram, is PtRepeatLoop -> translate(node) is PtLabel -> VmCodeChunk(VmCodeLabel(node.scopedName)) is PtBreakpoint -> VmCodeChunk(VmCodeInstruction(Opcode.BREAKPOINT)) + is PtConditionalBranch -> translate(node) is PtAddressOf, is PtContainmentCheck, is PtMemoryByte, @@ -99,7 +100,6 @@ class CodeGen(internal val program: PtProgram, is PtAsmSub -> throw AssemblyError("asmsub not supported on virtual machine target ${node.position}") is PtInlineAssembly -> throw AssemblyError("inline assembly not supported on virtual machine target ${node.position}") is PtIncludeBinary -> throw AssemblyError("inline binary data not supported on virtual machine target ${node.position}") - is PtConditionalBranch -> throw AssemblyError("conditional branches not supported in vm target due to lack of cpu flags ${node.position}") else -> TODO("missing codegen for $node") } if(code.lines.isNotEmpty() && node.position.line!=0) @@ -107,6 +107,33 @@ class CodeGen(internal val program: PtProgram, return code } + private fun translate(branch: PtConditionalBranch): VmCodeChunk { + val code = VmCodeChunk() + val elseLabel = createLabelName() + when(branch.condition) { + BranchCondition.CS -> { + code += VmCodeInstruction(Opcode.BSTCC, symbol = elseLabel) + } + BranchCondition.CC -> { + code += VmCodeInstruction(Opcode.BSTCS, symbol = elseLabel) + } + else -> { + throw AssemblyError("conditional branch ${branch.condition} not supported in vm target due to lack of cpu flags ${branch.position}") + } + } + code += translateNode(branch.trueScope) + if(branch.falseScope.children.isNotEmpty()) { + val endLabel = createLabelName() + code += VmCodeInstruction(Opcode.JUMP, symbol = endLabel) + code += VmCodeLabel(elseLabel) + code += translateNode(branch.falseScope) + code += VmCodeLabel(endLabel) + } else { + code += VmCodeLabel(elseLabel) + } + return code + } + private fun translate(whenStmt: PtWhen): VmCodeChunk { if(whenStmt.choices.children.isEmpty()) return VmCodeChunk() diff --git a/compiler/res/prog8lib/virtual/textio.p8 b/compiler/res/prog8lib/virtual/textio.p8 index f7793993e..24724ee99 100644 --- a/compiler/res/prog8lib/virtual/textio.p8 +++ b/compiler/res/prog8lib/virtual/textio.p8 @@ -90,11 +90,29 @@ sub print_ubhex (ubyte value, ubyte prefix) { sub print_ubbin (ubyte value, ubyte prefix) { ; ---- print the ubyte in binary form ; TODO use conv module? + if prefix + chrout('%') + repeat 8 { + rol(value) + if_cc + txt.chrout('0') + else + txt.chrout('1') + } } sub print_uwbin (uword value, ubyte prefix) { ; ---- print the uword in binary form ; TODO use conv module? + if prefix + chrout('%') + repeat 16 { + rol(value) + if_cc + txt.chrout('0') + else + txt.chrout('1') + } } sub print_uwhex (uword value, ubyte prefix) { diff --git a/examples/test.p8 b/examples/test.p8 index 06cf40567..6e653be5c 100644 --- a/examples/test.p8 +++ b/examples/test.p8 @@ -6,6 +6,7 @@ main { sub start() { + ; a "pixelshader": void syscall1(8, 0) ; enable lo res creen ubyte shifter diff --git a/syntax-files/IDEA/Prog8.xml b/syntax-files/IDEA/Prog8.xml index 877ec1b08..5952df9e7 100644 --- a/syntax-files/IDEA/Prog8.xml +++ b/syntax-files/IDEA/Prog8.xml @@ -14,7 +14,7 @@ - + diff --git a/virtualmachine/src/prog8/vm/Instructions.kt b/virtualmachine/src/prog8/vm/Instructions.kt index f05ea3fb0..7afeb8ffd 100644 --- a/virtualmachine/src/prog8/vm/Instructions.kt +++ b/virtualmachine/src/prog8/vm/Instructions.kt @@ -7,6 +7,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. +Status registers: Carry. Instruction serialization format possibility: @@ -59,8 +60,10 @@ return - restore last saved instruction location BRANCHING --------- -All have type b or w. +All have type b or w except the branches that only check status bits. +bstcc location - branch to location if Status bit Carry is Clear +bstcs location - branch to location if Status bit Carry is Set bz reg1, location - branch to location if reg1 is zero bnz reg1, location - branch to location if reg1 is not zero beq reg1, reg2, location - jump to location in program given by location, if reg1 == reg2 @@ -84,7 +87,7 @@ sgts reg1, reg2, reg3 - set reg=1 if reg2 > reg3 (signed), ot sge reg1, reg2, reg3 - set reg=1 if reg2 >= reg3 (unsigned), otherwise set reg1=0 sges reg1, reg2, reg3 - set reg=1 if reg2 >= reg3 (signed), otherwise set reg1=0 -TODO: support for the prog8 special branching instructions if_XX (bcc, bcs etc.) +TODO: support for the other prog8 special branching instructions if_XX (bpl, bmi etc.) but we don't have any 'processor flags' whatsoever in the vm so it's a bit weird @@ -105,7 +108,7 @@ mul reg1, reg2, reg3 - unsigned multiply reg1=reg2*reg3 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 -TODO signed mul/div/mod? +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. LOGICAL/BITWISE @@ -115,27 +118,29 @@ All have type b or w. 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, reg3 - reg1 = shift reg2 right by reg3 bits -asr reg1, reg2, reg3 - reg1 = shift reg2 right by reg3 bits (signed) -lsl reg1, reg2, reg3 - reg1 = shift reg2 left by reg3 bits -ror reg1, reg2, reg3 - reg1 = rotate reg2 right by reg3 bits, not using carry -rol reg1, reg2, reg3 - reg1 = rotate reg2 left by reg3 bits, not using carry - -TODO also add ror/rol variants using the carry bit? These do map directly on 6502 and 68k instructions. But the VM doesn't have carry status bit yet. +lsr reg1, reg2, reg3 - reg1 = shift reg2 right by reg3 bits + set Carry to shifted bit +asr reg1, reg2, reg3 - reg1 = shift reg2 right by reg3 bits (signed) + set Carry to shifted bit +lsl reg1, reg2, reg3 - reg1 = shift reg2 left by reg3 bits + set Carry to shifted bit +ror reg1 - rotate reg1 right by 1 bits, not using carry + set Carry to shifted bit +roxr reg1 - rotate reg1 right by 1 bits, using carry + set Carry to shifted bit +rol reg1 - rotate reg1 left by 1bits, not using carry + set Carry to shifted bit +roxl reg1 - rotate reg1 left by 1bits, using carry, + set Carry to shifted bit 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 - swap lsb and msb in register reg1 (16 bits) or lsw and msw (32 bits) -swapreg reg1, reg2 - swap values in reg1 and reg2 -concat [b, w] reg1, reg2, reg3 - reg1 = concatenated lsb/lsw of reg2 and lsb/lsw of reg3 into new word or int (int not yet implemented, requires 32bits regs) -push [b, w] reg1 - push value in reg1 on the stack -pop [b, w] reg1 - pop value from stack into reg1 +clc - clear Carry status bit +sec - set Carry status bit +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 - swap lsb and msb in register reg1 (16 bits) or lsw and msw (32 bits) +swapreg reg1, reg2 - swap values in reg1 and reg2 +concat [b, w] reg1, reg2, reg3 - reg1 = concatenated lsb/lsw of reg2 and lsb/lsw of reg3 into new word or int (int not yet implemented, requires 32bits regs) +push [b, w] reg1 - push value in reg1 on the stack +pop [b, w] reg1 - pop value from stack into reg1 */ @@ -159,6 +164,9 @@ enum class Opcode { CALLI, SYSCALL, RETURN, + + BSTCC, + BSTCS, BZ, BNZ, BEQ, @@ -202,8 +210,12 @@ enum class Opcode { LSR, LSL, ROR, + ROXR, ROL, + ROXL, + CLC, + SEC, PUSH, POP, SWAP, @@ -249,6 +261,11 @@ data class Instruction( format.reg3 && reg3==null) throw IllegalArgumentException("missing a register") + if(!format.reg1 && reg1!=null || + !format.reg2 && reg2!=null || + !format.reg3 && reg3!=null) + throw IllegalArgumentException("too many registers") + if(format.value && (value==null && symbol==null)) throw IllegalArgumentException("missing a value or symbol") } @@ -313,6 +330,9 @@ val instructionFormats = mutableMapOf( Opcode.CALLI 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.BSTCC to InstructionFormat(NN, false, false, false, true ), + Opcode.BSTCS to InstructionFormat(NN, false, false, false, true ), 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 ), @@ -355,8 +375,10 @@ val instructionFormats = mutableMapOf( Opcode.ASR to InstructionFormat(BW, true, true, true, false), Opcode.LSR to InstructionFormat(BW, true, true, true, false), Opcode.LSL to InstructionFormat(BW, true, true, true, false), - Opcode.ROR to InstructionFormat(BW, true, true, true, false), - Opcode.ROL to InstructionFormat(BW, true, true, true, false), + Opcode.ROR to InstructionFormat(BW, true, false, false, false), + Opcode.ROXR 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.COPY to InstructionFormat(NN, true, true, false, true ), Opcode.COPYZ to InstructionFormat(NN, true, true, false, false), @@ -364,5 +386,7 @@ val instructionFormats = mutableMapOf( Opcode.PUSH to InstructionFormat(BW, true, false, false, false), Opcode.POP to InstructionFormat(BW, true, false, false, false), Opcode.CONCAT to InstructionFormat(BW, true, true, true, false), + Opcode.CLC to InstructionFormat(NN, false, false, false, false), + Opcode.SEC to InstructionFormat(NN, false, false, false, false), Opcode.BREAKPOINT to InstructionFormat(NN, false, false, false, false) ) diff --git a/virtualmachine/src/prog8/vm/VirtualMachine.kt b/virtualmachine/src/prog8/vm/VirtualMachine.kt index fffd82141..798250c37 100644 --- a/virtualmachine/src/prog8/vm/VirtualMachine.kt +++ b/virtualmachine/src/prog8/vm/VirtualMachine.kt @@ -19,6 +19,7 @@ class VirtualMachine(val memory: Memory, program: List) { val valueStack = Stack() // max 128 entries var pc = 0 var stepCount = 0 + var statusCarry = false init { if(program.size>65536) @@ -58,6 +59,7 @@ class VirtualMachine(val memory: Memory, program: List) { pc = 0 stepCount = 0 callStack.clear() + statusCarry = false } fun exit() { @@ -97,6 +99,8 @@ class VirtualMachine(val memory: Memory, program: List) { Opcode.CALLI -> InsCALLI(ins) Opcode.SYSCALL -> InsSYSCALL(ins) Opcode.RETURN -> InsRETURN() + Opcode.BSTCC -> InsBSTCC(ins) + Opcode.BSTCS -> InsBSTCS(ins) Opcode.BZ -> InsBZ(ins) Opcode.BNZ -> InsBNZ(ins) Opcode.BEQ -> InsBEQ(ins) @@ -138,8 +142,10 @@ class VirtualMachine(val memory: Memory, program: List) { Opcode.ASR -> InsASR(ins) Opcode.LSR -> InsLSR(ins) Opcode.LSL -> InsLSL(ins) - Opcode.ROR -> InsROR(ins) - Opcode.ROL -> InsROL(ins) + Opcode.ROR -> InsROR(ins, false) + Opcode.ROXR -> InsROR(ins, true) + Opcode.ROL -> InsROL(ins, false) + Opcode.ROXL -> InsROL(ins, true) Opcode.SWAP -> InsSWAP(ins) Opcode.CONCAT -> InsCONCAT(ins) Opcode.PUSH -> InsPUSH(ins) @@ -312,6 +318,20 @@ class VirtualMachine(val memory: Memory, program: List) { pc = callStack.pop() } + private fun InsBSTCC(i: Instruction) { + if(!statusCarry) + pc = i.value!! + else + pc++ + } + + private fun InsBSTCS(i: Instruction) { + if(statusCarry) + pc = i.value!! + else + pc++ + } + private fun InsBZ(i: Instruction) { when(i.type!!) { VmDataType.BYTE -> { @@ -666,6 +686,7 @@ class VirtualMachine(val memory: Memory, program: List) { private fun InsASR(i: Instruction) { val (left: Int, right: Int) = getLogicalOperandsS(i) + statusCarry = (left and 1)!=0 when(i.type!!) { VmDataType.BYTE -> registers.setSB(i.reg1!!, (left shr right).toByte()) VmDataType.WORD -> registers.setSW(i.reg1!!, (left shr right).toShort()) @@ -675,6 +696,7 @@ class VirtualMachine(val memory: Memory, program: List) { private fun InsLSR(i: Instruction) { val (left: UInt, right: UInt) = getLogicalOperandsU(i) + statusCarry = (left and 1u)!=0u when(i.type!!) { VmDataType.BYTE -> registers.setUB(i.reg1!!, (left shr right.toInt()).toUByte()) VmDataType.WORD -> registers.setUW(i.reg1!!, (left shr right.toInt()).toUShort()) @@ -685,28 +707,72 @@ class VirtualMachine(val memory: Memory, program: List) { private fun InsLSL(i: Instruction) { val (left: UInt, right: UInt) = getLogicalOperandsU(i) when(i.type!!) { - VmDataType.BYTE -> registers.setUB(i.reg1!!, (left shl right.toInt()).toUByte()) - VmDataType.WORD -> registers.setUW(i.reg1!!, (left shl right.toInt()).toUShort()) + VmDataType.BYTE -> { + statusCarry = (left and 0x80u)!=0u + registers.setUB(i.reg1!!, (left shl right.toInt()).toUByte()) + } + VmDataType.WORD -> { + statusCarry = (left and 0x8000u)!=0u + registers.setUW(i.reg1!!, (left shl right.toInt()).toUShort()) + } } pc++ } - private fun InsROR(i: Instruction) { - val (left: UInt, right: UInt) = getLogicalOperandsU(i) - when(i.type!!) { - VmDataType.BYTE -> registers.setUB(i.reg1!!, (left.rotateRight(right.toInt()).toUByte())) - VmDataType.WORD -> registers.setUW(i.reg1!!, (left.rotateRight(right.toInt()).toUShort())) + private fun InsROR(i: Instruction, useCarry: Boolean) { + val newStatusCarry: Boolean + when (i.type!!) { + VmDataType.BYTE -> { + val orig = registers.getUB(i.reg1!!) + newStatusCarry = (orig.toInt() and 1) != 0 + val rotated: UByte = if (useCarry) { + val carry = if (statusCarry) 0x80u else 0x00u + (orig.toUInt().rotateRight(1) or carry).toUByte() + } else + orig.rotateRight(1) + registers.setUB(i.reg1, rotated) + } + VmDataType.WORD -> { + val orig = registers.getUW(i.reg1!!) + newStatusCarry = (orig.toInt() and 1) != 0 + val rotated: UShort = if (useCarry) { + val carry = if (statusCarry) 0x8000u else 0x0000u + (orig.toUInt().rotateRight(1) or carry).toUShort() + } else + orig.rotateRight(1) + registers.setUW(i.reg1, rotated) + } } pc++ + statusCarry = newStatusCarry } - private fun InsROL(i: Instruction) { - val (left: UInt, right: UInt) = getLogicalOperandsU(i) - when(i.type!!) { - VmDataType.BYTE -> registers.setUB(i.reg1!!, (left.rotateLeft(right.toInt()).toUByte())) - VmDataType.WORD -> registers.setUW(i.reg1!!, (left.rotateLeft(right.toInt()).toUShort())) + private fun InsROL(i: Instruction, useCarry: Boolean) { + val newStatusCarry: Boolean + when (i.type!!) { + VmDataType.BYTE -> { + val orig = registers.getUB(i.reg1!!) + newStatusCarry = (orig.toInt() and 0x80) != 0 + val rotated: UByte = if (useCarry) { + val carry = if (statusCarry) 1u else 0u + (orig.toUInt().rotateLeft(1) or carry).toUByte() + } else + orig.rotateLeft(1) + registers.setUB(i.reg1, rotated) + } + VmDataType.WORD -> { + val orig = registers.getUW(i.reg1!!) + newStatusCarry = (orig.toInt() and 0x8000) != 0 + val rotated: UShort = if (useCarry) { + val carry = if (statusCarry) 1u else 0u + (orig.toUInt().rotateLeft(1) or carry).toUShort() + } else + orig.rotateLeft(1) + registers.setUW(i.reg1, rotated) + } } pc++ + statusCarry = newStatusCarry } private fun InsSWAP(i: Instruction) {