vm: implementing rol/ror

This commit is contained in:
Irmen de Jong 2022-04-09 00:49:23 +02:00
parent a8cf9f5cc4
commit a0face4a28
7 changed files with 192 additions and 42 deletions

View File

@ -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)
}
}

View File

@ -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()

View File

@ -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) {

View File

@ -6,6 +6,7 @@
main {
sub start() {
; a "pixelshader":
void syscall1(8, 0) ; enable lo res creen
ubyte shifter

View File

@ -14,7 +14,7 @@
<keywords keywords="&amp;;-&gt;;@;\$;and;as;asmsub;break;clobbers;do;downto;else;false;for;goto;if;if_cc;if_cs;if_eq;if_mi;if_ne;if_neg;if_nz;if_pl;if_pos;if_vc;if_vs;if_z;in;inline;not;or;repeat;return;romsub;step;sub;to;true;until;when;while;xor;~" ignore_case="false" />
<keywords2 keywords="%address;%asm;%asmbinary;%asminclude;%breakpoint;%import;%launcher;%option;%output;%zeropage;%zpreserved;iso:;petscii:;sc:" />
<keywords3 keywords="@requirezp;@shared;@zp;byte;const;float;str;ubyte;uword;void;word" />
<keywords4 keywords="abs;acos;all;any;asin;atan;avg;callfar;callrom;ceil;cmp;cos;cos16;cos16u;cos8;cos8u;cosr16;cosr16u;cosr8;cosr8u;deg;floor;len;ln;log2;lsb;lsl;lsr;max;memory;min;mkword;msb;peek;peekw;poke;pokew;pop;popw;push;pushw;rad;reverse;rnd;rndf;rndw;rol;rol2;ror;ror2;round;rrestore;rrestorex;rsave;rsavex;sgn;sin;sin16;sin16u;sin8;sin8u;sinr16;sinr16u;sinr8;sinr8u;sizeof;sort;sqrt;sqrt16;sum;swap;tan;|&gt;" />
<keywords4 keywords="abs;acos;all;any;asin;atan;avg;callfar;callrom;ceil;cmp;cos;cos16;cos16u;cos8;cos8u;cosr16;cosr16u;cosr8;cosr8u;deg;floor;len;ln;log2;lsb;max;memory;min;mkword;msb;peek;peekw;poke;pokew;pop;popw;push;pushw;rad;reverse;rnd;rndf;rndw;rol;rol2;ror;ror2;round;rrestore;rrestorex;rsave;rsavex;sgn;sin;sin16;sin16u;sin8;sin8u;sinr16;sinr16u;sinr8;sinr8u;sizeof;sort;sqrt;sqrt16;sum;swap;tan;|&gt;" />
</highlighting>
<extensionMap>
<mapping ext="p8" />

View File

@ -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)
)

View File

@ -19,6 +19,7 @@ class VirtualMachine(val memory: Memory, program: List<Instruction>) {
val valueStack = Stack<Int>() // 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<Instruction>) {
pc = 0
stepCount = 0
callStack.clear()
statusCarry = false
}
fun exit() {
@ -97,6 +99,8 @@ class VirtualMachine(val memory: Memory, program: List<Instruction>) {
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<Instruction>) {
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<Instruction>) {
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<Instruction>) {
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<Instruction>) {
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<Instruction>) {
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) {