package prog8.intermediate import prog8.code.core.RegisterOrStatusflag import prog8.code.core.toHex /* Intermediate Representation instructions for the IR Virtual machine. -------------------------------------------------------------------- Specs of the virtual machine this will run on: Program to execute is not stored in the system memory, it's just a separate list of instructions. 65536 virtual registers, 16 bits wide, can also be used as 8 bits. r0-r65535 65536 virtual floating point registers (64 bits double precision) fr0-fr65535 65536 bytes of memory. Thus memory pointers (addresses) are limited to 16 bits. Value stack, max 128 entries of 1 byte each. Status flags: Carry, Zero, Negative. NOTE: status flags are only affected by the CMP instruction or explicit CLC/SEC, LOAD instructions DO affect the Z and N flags. INC/DEC/NEG instructions DO affect the Z and N flags, other instructions only affect Z an N flags if the value in a result register is written. See OpcodesThatSetStatusbits Instruction set is mostly a load/store architecture, there are few instructions operating on memory directly. Value types: integers (.b=byte=8 bits, .w=word=16 bits) and float (.f=64 bits). Omitting it defaults to b if the instruction requires a type. Currently ther is NO support for 24 or 32 bits integers. There is no distinction between signed and unsigned integers. Instead, a different instruction is used if a distinction should be made (for example div and divs). Floating point operations are just 'f' typed regular instructions, however there are a few unique fp conversion instructions. NOTE: Labels in source text should always start with an underscore. LOAD/STORE ---------- All have type b or w or f. load reg1, value - load immediate value into register. If you supply a symbol, loads the *address* of the symbol! (variable values are loaded from memory via the loadm instruction) loadm reg1, address - load reg1 with value at memory address loadi reg1, reg2 - load reg1 with value at memory indirect, memory pointed to by reg2 loadx reg1, reg2, address - load reg1 with value at memory address indexed by value in reg2 (only the lsb part used for indexing) loadix reg1, reg2, pointeraddr - load reg1 with value at memory indirect, pointed to by pointeraddr indexed by value in reg2 (only the lsb part used for indexing) loadr reg1, reg2 - load reg1 with value in register reg2 loadha reg1 - load cpu hardware register A into reg1.b loadhx reg1 - load cpu hardware register X into reg1.b loadhy reg1 - load cpu hardware register Y into reg1.b loadhax reg1 - load cpu hardware register pair AX into reg1.w loadhay reg1 - load cpu hardware register pair AY into reg1.w loadhxy reg1 - load cpu hardware register pair XY into reg1.w storem reg1, address - store reg1 at memory address storei reg1, reg2 - store reg1 at memory indirect, memory pointed to by reg2 storex reg1, reg2, address - store reg1 at memory address, indexed by value in reg2 (only the lsb part used for indexing) storeix reg1, reg2, pointeraddr - store reg1 at memory indirect, pointed to by pointeraddr indexed by value in reg2 (only the lsb part used for indexing) storezm address - store zero at memory address storezi reg1 - store zero at memory pointed to by reg1 storezx reg1, address - store zero at memory address, indexed by value in reg1 (only the lsb part used for indexing) storeha reg1 - store reg1.b into cpu hardware register A storehx reg1 - store reg1.b into cpu hardware register X storehy reg1 - store reg1.b into cpu hardware register Y storehax reg1 - store reg1.w into cpu hardware register pair AX storehay reg1 - store reg1.w into cpu hardware register pair AY storehxy reg1 - store reg1.w into cpu hardware register pair XY CONTROL FLOW ------------ jump location - continue running at instruction at 'location' (label/memory address) jumpi pointervar - continue running at memory address contained in the pointer variable (indirect jump) preparecall numparams - indicator that the next instructions are the param setup and function call/syscall with parameters calli reg1 - calls a subroutine (without arguments and without return valus) at memory addres in reg1 (indirect jsr) call label(argument register list) [: resultreg.type] - calls a subroutine with the given arguments and return value (optional). save current instruction location+1, continue execution at instruction nr of the label. the argument register list is positional and includes the datatype, ex.: r4.b,r5.w,fp1.f If the call is to a rom-routine, 'label' will be a hexadecimal address instead such as $ffd2 If the arguments should be passed in CPU registers, they'll have a @REGISTER postfix. For example: call $ffd2(r5.b@A) Always preceded by parameter setup and preparecall instructions syscall number (argument register list) [: resultreg.type] - do a systemcall identified by number, result value(s) are pushed on value stack by the syscall code so will be POPped off into the given resultregister if any. Always preceded by parameter setup and preparecall instructions return - restore last saved instruction location and continue at that instruction. No return value. returnr reg1 - like return, but also returns the value in reg1 to the caller BRANCHING and CONDITIONALS -------------------------- All have type b or w except the branches that only check status bits. bstcc address - branch to location if Status bit Carry is clear bstcs address - branch to location if Status bit Carry is set bstne address - branch to location if Status bit Zero is clear bsteq address - branch to location if Status bit Zero is set bstpos address - branch to location if Status bit Negative is clear bstneg address - branch to location if Status bit Negative is set bstvc address - branch to location if Status bit Overflow is clear bstvs address - branch to location if Status bit Overflow is set (unsigned comparison branches:) bgt reg1, value, address - jump to location in program given by location, if reg1 > immediate value (unsigned) blt reg1, value, address - jump to location in program given by location, if reg1 < immediate value (unsigned) bgtr reg1, reg2, address - jump to location in program given by location, if reg1 > reg2 (unsigned) 'bltr' reg1, reg2, address - jump to location in program given by location, if reg1 < reg2 (unsigned) ==> use bgtr with swapped operands bge reg1, value, address - jump to location in program given by location, if reg1 >= immediate value (unsigned) ble reg1, value, address - jump to location in program given by location, if reg1 <= immediate value (unsigned) bger reg1, reg2, address - jump to location in program given by location, if reg1 >= reg2 (unsigned) 'bler' reg1, reg2, address - jump to location in program given by location, if reg1 <= reg2 (unsigned) ==> use bger with swapped operands (signed comparison branches:) bgts reg1, value, address - jump to location in program given by location, if reg1 > immediate value (signed) blts reg1, value, address - jump to location in program given by location, if reg1 < immediate value (signed) bgtsr reg1, reg2, address - jump to location in program given by location, if reg1 > reg2 (signed) 'bltsr' reg1, reg2, address - jump to location in program given by location, if reg1 < reg2 (signed) ==> use bgtsr with swapped operands bges reg1, value, address - jump to location in program given by location, if reg1 >= immediate value (signed) bles reg1, value, address - jump to location in program given by location, if reg1 <= immediate value (signed) bgesr reg1, reg2, address - jump to location in program given by location, if reg1 >= reg2 (signed) 'blesr' reg1, reg2, address - jump to location in program given by location, if reg1 <= reg2 (signed) ==> use bgesr with swapped operands scc reg1 - set reg1=1 if Carry flag is clear, else 0 scs reg1 - set reg1=1 if Carry flag is set, else 0 sz reg1, reg2 - set reg1=1 if reg2==0, else 0 snz reg1, reg2 - set reg1=1 if reg2!=0, else 0 seq reg1, reg2, reg3 - set reg1=1 if reg2 == reg3, else 0 sne reg1, reg2, reg3 - set reg1=1 if reg2 != reg3, else 0 slt reg1, reg2, reg3 - set reg1=1 if reg2 < reg3 (unsigned), else 0 slts reg1, reg2, reg3 - set reg1=1 if reg2 < reg3 (signed), else 0 sle reg1, reg2, reg3 - set reg1=1 if reg2 <= reg3 (unsigned), else 0 sles reg1, reg2, reg3 - set reg1=1 if reg2 <= reg3 (signed), else 0 sgt reg1, reg2, reg3 - set reg1=1 if reg2 > reg3 (unsigned), else 0 sgts reg1, reg2, reg3 - set reg1=1 if reg2 > reg3 (signed), else 0 sge reg1, reg2, reg3 - set reg1=1 if reg2 >= reg3 (unsigned), else 0 sges reg1, reg2, reg3 - set reg1=1 if reg2 >= reg3 (signed), else 0 (note: on the M68k these instructions will set all bits to 1 (so value=-1 instead of 1), but the boolean logic here requires it to be 0 or 1 in this IR) ARITHMETIC ---------- All have type b or w or f. Note: result types are the same as operand types! E.g. byte*byte->byte. exts reg1, reg2 - reg1 = signed extension of reg2 (byte to word, or word to long) (note: unlike M68k, exts.b -> word and exts.w -> long. The latter is not yet implemented yet as we don't have longs yet) ext reg1, reg2 - reg1 = unsigned extension of reg2 (which in practice just means clearing the MSB / MSW) (note: unlike M68k, ext.b -> word and ext.w -> long. The latter is not yet implemented yet as we don't have longs yet) inc reg1 - reg1 = reg1+1 incm address - memory at address += 1 dec reg1 - reg1 = reg1-1 decm address - memory at address -= 1 neg reg1 - reg1 = sign negation of reg1 negm address - sign negate memory at address addr reg1, reg2 - reg1 += reg2 add reg1, value - reg1 += value addm reg1, address - memory at address += reg1 subr reg1, reg2 - reg1 -= reg2 sub reg1, value - reg1 -= value subm reg1, address - memory at address -= reg1 mulr reg1, reg2 - unsigned multiply reg1 *= reg2 note: byte*byte->byte, no type extension to word! mul reg1, value - unsigned multiply reg1 *= value note: byte*byte->byte, no type extension to word! mulm reg1, address - memory at address *= reg2 note: byte*byte->byte, no type extension to word! divr reg1, reg2 - unsigned division reg1 /= reg2 note: division by zero yields max int $ff/$ffff div reg1, value - unsigned division reg1 /= value note: division by zero yields max int $ff/$ffff divm reg1, address - memory at address /= reg2 note: division by zero yields max int $ff/$ffff divsr reg1, reg2 - signed division reg1 /= reg2 note: division by zero yields max signed int 127 / 32767 divs reg1, value - signed division reg1 /= value note: division by zero yields max signed int 127 / 32767 divsm reg1, address - signed memory at address /= reg2 note: division by zero yields max signed int 127 / 32767 modr reg1, reg2 - remainder (modulo) of unsigned division reg1 %= reg2 note: division by zero yields max signed int $ff/$ffff mod reg1, value - remainder (modulo) of unsigned division reg1 %= value note: division by zero yields max signed int $ff/$ffff divmodr reg1, reg2 - unsigned division reg1/reg2, storing division and remainder on value stack (so need to be POPped off) divmod reg1, value - unsigned division reg1/value, storing division and remainder on value stack (so need to be POPped off) sqrt reg1, reg2 - reg1 is the square root of reg2 (reg2 can be .w or .b, result type in reg1 is always .b) you can also use it with floating point types, fpreg1 and fpreg2 (result is also .f) square reg1, reg2 - reg1 is the square of reg2 (reg2 can be .w or .b, result type in reg1 is always .b) you can also use it with floating point types, fpreg1 and fpreg2 (result is also .f) sgn reg1, reg2 - reg1.b is the sign of reg2 (or fpreg1, if sgn.f) (0.b, 1.b or -1.b) cmp reg1, reg2 - set processor status bits C, N, Z according to comparison of reg1 with reg2. (semantics taken from 6502/68000 CMP instruction) cmpi reg1, value - set processor status bits C, N, Z according to comparison of reg1 with immediate value. (semantics taken from 6502/68000 CMP instruction) 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 --------------- All have type b or w. andr reg1, reg2 - reg1 = reg1 bitwise and reg2 and reg1, value - reg1 = reg1 bitwise and value andm reg1 address - memory = memory bitwise and reg1 orr reg1, reg2 - reg1 = reg1 bitwise or reg2 or reg1, value - reg1 = reg1 bitwise or value orm reg1, address - memory = memory bitwise or reg1 xorr reg1, reg2 - reg1 = reg1 bitwise xor reg2 xor reg1, value - reg1 = reg1 bitwise xor value xorm reg1, address - memory = memory bitwise xor reg1 inv reg1 - reg1 = bitwise invert of reg1 (all bits flipped) invm address - memory = bitwise invert of that memory (all bits flipped) asrn reg1, reg2 - reg1 = multi-shift reg1 right by reg2 bits (signed) + set Carry to shifted bit lsrn reg1, reg2 - reg1 = multi-shift reg1 right by reg2 bits + set Carry to shifted bit lsln reg1, reg2 - reg1 = multi-shift reg1 left by reg2 bits + set Carry to shifted bit asrnm reg1, address - multi-shift memory right by reg1 bits (signed) + set Carry to shifted bit lsrnm reg1, address - multi-shift memoryright by reg1 bits + set Carry to shifted bit lslnm reg1, address - multi-shift memory left by reg1 bits + set Carry to shifted bit asr reg1 - shift reg1 right by 1 bits (signed) + set Carry to shifted bit lsr reg1 - shift reg1 right by 1 bits + set Carry to shifted bit lsl reg1 - shift reg1 left by 1 bits + set Carry to shifted bit lsrm address - shift memory right by 1 bits + set Carry to shifted bit asrm address - shift memory right by 1 bits (signed) + set Carry to shifted bit lslm address - shift memory left by 1 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 1 bits, not using carry + set Carry to shifted bit roxl reg1 - rotate reg1 left by 1 bits, using carry, + set Carry to shifted bit rorm address - rotate memory right by 1 bits, not using carry + set Carry to shifted bit roxrm address - rotate memory right by 1 bits, using carry + set Carry to shifted bit rolm address - rotate memory left by 1 bits, not using carry + set Carry to shifted bit roxlm address - rotate memory left by 1 bits, using carry, + set Carry to shifted bit FLOATING POINT CONVERSIONS AND FUNCTIONS ---------------------------------------- ffromub fpreg1, reg1 - fpreg1 = reg1 from usigned byte ffromsb fpreg1, reg1 - fpreg1 = reg1 from signed byte ffromuw fpreg1, reg1 - fpreg1 = reg1 from unsigned word ffromsw fpreg1, reg1 - fpreg1 = reg1 from signed word ftoub reg1, fpreg1 - reg1 = fpreg1 as unsigned byte ftosb reg1, fpreg1 - reg1 = fpreg1 as signed byte ftouw reg1, fpreg1 - reg1 = fpreg1 as unsigned word ftosw reg1, fpreg1 - reg1 = fpreg1 as signed word fpow fpreg1, fpreg2 - fpreg1 = fpreg1 to the power of fpreg2 fabs fpreg1, fpreg2 - fpreg1 = abs(fpreg2) fcomp reg1, fpreg1, fpreg2 - reg1 = result of comparison of fpreg1 and fpreg2: 0.b=equal, 1.b=fpreg1 is greater, -1.b=fpreg1 is smaller fsin fpreg1, fpreg2 - fpreg1 = sin(fpreg2) fcos fpreg1, fpreg2 - fpreg1 = cos(fpreg2) ftan fpreg1, fpreg2 - fpreg1 = tan(fpreg2) fatan fpreg1, fpreg2 - fpreg1 = atan(fpreg2) fln fpreg1, fpreg2 - fpreg1 = ln(fpreg2) ; natural logarithm flog fpreg1, fpreg2 - fpreg1 = log(fpreg2) ; base 2 logarithm fround fpreg1, fpreg2 - fpreg1 = round(fpreg2) ffloor fpreg1, fpreg2 - fpreg1 = floor(fpreg2) fceil fpreg1, fpreg2 - fpreg1 = ceil(fpreg2) MISC ---- clc - clear Carry status bit sec - set Carry status bit nop - do nothing breakpoint - trigger a breakpoint msig [b, w] reg1, reg2 - reg1 becomes the most significant byte (or word) of the word (or int) in reg2 (.w not yet implemented; requires 32 bits regs) concat [b, w] reg1, reg2, reg3 - reg1.w = 'concatenate' two registers: lsb/lsw of reg2 (as msb) and lsb/lsw of reg3 (as lsb) into word or int (int not yet implemented; requires 32bits regs) push [b, w, f] reg1 - push value in reg1 on the stack pop [b, w, f] reg1 - pop value from stack into reg1 pushst - push status register bits to stack popst - pop status register bits from stack */ enum class Opcode { NOP, LOAD, // note: LOAD gets you the address of the symbol, whereas LOADM would get you the value stored at that location LOADM, LOADI, LOADX, LOADIX, LOADR, LOADHA, LOADHX, LOADHY, LOADHAX, LOADHAY, LOADHXY, STOREM, STOREI, STOREX, STOREIX, STOREZM, STOREZI, STOREZX, STOREHA, STOREHX, STOREHY, STOREHAX, STOREHAY, STOREHXY, JUMP, JUMPI, PREPARECALL, CALLI, CALL, SYSCALL, RETURN, RETURNR, BSTCC, BSTCS, BSTEQ, BSTNE, BSTNEG, BSTPOS, BSTVC, BSTVS, BGTR, BGT, BLT, BGTSR, BGTS, BLTS, BGER, BGE, BLE, BGESR, BGES, BLES, SCC, SCS, SZ, SNZ, SEQ, SNE, SLT, SLTS, SGT, SGTS, SLE, SLES, SGE, SGES, INC, INCM, DEC, DECM, NEG, NEGM, ADDR, ADD, ADDM, SUBR, SUB, SUBM, MULR, MUL, MULM, DIVR, DIV, DIVM, DIVSR, DIVS, DIVSM, MODR, MOD, DIVMODR, DIVMOD, SQRT, SQUARE, SGN, CMP, CMPI, EXT, EXTS, ANDR, AND, ANDM, ORR, OR, ORM, XORR, XOR, XORM, INV, INVM, ASRN, ASRNM, LSRN, LSRNM, LSLN, LSLNM, ASR, ASRM, LSR, LSRM, LSL, LSLM, ROR, RORM, ROXR, ROXRM, ROL, ROLM, ROXL, ROXLM, FFROMUB, FFROMSB, FFROMUW, FFROMSW, FTOUB, FTOSB, FTOUW, FTOSW, FPOW, FABS, FSIN, FCOS, FTAN, FATAN, FLN, FLOG, FROUND, FFLOOR, FCEIL, FCOMP, CLC, SEC, PUSH, POP, PUSHST, POPST, MSIG, CONCAT, BREAKPOINT } val OpcodesThatJump = arrayOf( Opcode.JUMP, Opcode.JUMPI, Opcode.RETURN, Opcode.RETURNR ) val OpcodesThatBranch = arrayOf( Opcode.JUMP, Opcode.JUMPI, Opcode.RETURN, Opcode.RETURNR, Opcode.CALLI, Opcode.CALL, Opcode.SYSCALL, Opcode.BSTCC, Opcode.BSTCS, Opcode.BSTEQ, Opcode.BSTNE, Opcode.BSTNEG, Opcode.BSTPOS, Opcode.BSTVC, Opcode.BSTVS, Opcode.BGTR, Opcode.BGT, Opcode.BLT, Opcode.BGTSR, Opcode.BGTS, Opcode.BLTS, Opcode.BGER, Opcode.BGE, Opcode.BLE, Opcode.BGESR, Opcode.BGES, Opcode.BLES ) val OpcodesThatSetStatusbitsIncludingCarry = arrayOf( Opcode.CMP, Opcode.CMPI ) val OpcodesThatSetStatusbitsButNotCarry = arrayOf( Opcode.LOAD, Opcode.LOADM, Opcode.LOADI, Opcode.LOADX, Opcode.LOADIX, Opcode.LOADR, Opcode.LOADHA, Opcode.LOADHX, Opcode.LOADHY, Opcode.LOADHAX, Opcode.LOADHAY, Opcode.LOADHXY, Opcode.NEG, Opcode.NEGM, Opcode.INC, Opcode.INCM, Opcode.DEC, Opcode.DECM, Opcode.ANDM, Opcode.ANDR, Opcode.AND, Opcode.ORM, Opcode.ORR, Opcode.OR, Opcode.XORM, Opcode.XORR, Opcode.XOR, ) val OpcodesThatDependOnCarry = arrayOf( Opcode.BSTCC, Opcode.BSTCS, Opcode.BSTPOS, Opcode.BSTNEG, Opcode.ROXL, Opcode.ROXLM, Opcode.ROXR, Opcode.ROXRM, ) val OpcodesThatSetRegFromStatusbits = arrayOf( Opcode.SCC, Opcode.SCS, Opcode.SZ, Opcode.SNZ, Opcode.SEQ, Opcode.SNE, Opcode.SLT, Opcode.SLTS, Opcode.SGT, Opcode.SGTS, Opcode.SLE, Opcode.SLES, Opcode.SGE, Opcode.SGES ) val OpcodesThatSetStatusbits = OpcodesThatSetStatusbitsButNotCarry + OpcodesThatSetStatusbitsIncludingCarry enum class IRDataType { BYTE, WORD, FLOAT // TODO add INT (32-bit)? INT24 (24-bit)? } enum class OperandDirection { UNUSED, READ, WRITE, READWRITE } data class InstructionFormat(val datatype: IRDataType?, val reg1: OperandDirection, val reg2: OperandDirection, val reg3: OperandDirection, val fpReg1: OperandDirection, val fpReg2: OperandDirection, val address: OperandDirection, val immediate: Boolean, val funcCall: Boolean, val sysCall: Boolean) { companion object { fun from(spec: String): Map { val result = mutableMapOf() for(part in spec.split('|').map{ it.trim() }) { var reg1 = OperandDirection.UNUSED var reg2 = OperandDirection.UNUSED var reg3 = OperandDirection.UNUSED var fpreg1 = OperandDirection.UNUSED var fpreg2 = OperandDirection.UNUSED var address = OperandDirection.UNUSED var immediate = false val splits = part.splitToSequence(',').iterator() val typespec = splits.next() var funcCall = false var sysCall = false while(splits.hasNext()) { when(splits.next()) { " reg1 = OperandDirection.READ ">r1" -> reg1 = OperandDirection.WRITE "<>r1" -> reg1 = OperandDirection.READWRITE " reg2 = OperandDirection.READ " reg3 = OperandDirection.READ " fpreg1 = OperandDirection.READ ">fr1" -> fpreg1 = OperandDirection.WRITE "<>fr1" -> fpreg1 = OperandDirection.READWRITE " fpreg2 = OperandDirection.READ ">i", "<>i" -> throw IllegalArgumentException("can't write into an immediate value") " immediate = true " address = OperandDirection.READ ">a" -> address = OperandDirection.WRITE "<>a" -> address = OperandDirection.READWRITE "call" -> funcCall = true "syscall" -> sysCall = true else -> throw IllegalArgumentException(spec) } } if(typespec=="N") result[null] = InstructionFormat(null, reg1, reg2, reg3, fpreg1, fpreg2, address, immediate, funcCall, sysCall) if('B' in typespec) result[IRDataType.BYTE] = InstructionFormat(IRDataType.BYTE, reg1, reg2, reg3, fpreg1, fpreg2, address, immediate, funcCall, sysCall) if('W' in typespec) result[IRDataType.WORD] = InstructionFormat(IRDataType.WORD, reg1, reg2, reg3, fpreg1, fpreg2, address, immediate, funcCall, sysCall) if('F' in typespec) result[IRDataType.FLOAT] = InstructionFormat(IRDataType.FLOAT, reg1, reg2, reg3, fpreg1, fpreg2, address, immediate, funcCall, sysCall) } return result } } } /* X = X is overwritten with output value (write value) <>X = X is modified (read + written) where X is one of: r0... = integer register fr0... = fp register a = memory address i = immediate value */ val instructionFormats = mutableMapOf( Opcode.NOP to InstructionFormat.from("N"), Opcode.LOAD to InstructionFormat.from("BW,>r1,fr1,r1,fr1,r1,fr1,r1,fr1,r1,fr1,r1,fr1,r1"), Opcode.LOADHA to InstructionFormat.from("B,>r1"), Opcode.LOADHX to InstructionFormat.from("B,>r1"), Opcode.LOADHY to InstructionFormat.from("B,>r1"), Opcode.LOADHAX to InstructionFormat.from("W,>r1"), Opcode.LOADHAY to InstructionFormat.from("W,>r1"), Opcode.LOADHXY to InstructionFormat.from("W,>r1"), Opcode.STOREM to InstructionFormat.from("BW,a | F,a"), Opcode.STOREI to InstructionFormat.from("BW,a | F,a"), Opcode.STOREIX to InstructionFormat.from("BW,a | F,a"), Opcode.STOREZM to InstructionFormat.from("BW,>a | F,>a"), Opcode.STOREZI to InstructionFormat.from("BW,a | F,a"), Opcode.STOREHA to InstructionFormat.from("B,r1 | F,>fr1"), Opcode.BSTCC to InstructionFormat.from("N,r1"), Opcode.SCS to InstructionFormat.from("BW,>r1"), Opcode.SZ to InstructionFormat.from("BW,>r1,r1,r1,r1,r1,r1,r1,r1,r1,r1,r1,r1,r1 | F,<>fr1"), Opcode.INCM to InstructionFormat.from("BW,<>a | F,<>a"), Opcode.DEC to InstructionFormat.from("BW,<>r1 | F,<>fr1"), Opcode.DECM to InstructionFormat.from("BW,<>a | F,<>a"), Opcode.NEG to InstructionFormat.from("BW,<>r1 | F,<>fr1"), Opcode.NEGM to InstructionFormat.from("BW,<>a | F,<>a"), Opcode.ADDR to InstructionFormat.from("BW,<>r1,fr1,r1,fr1,a | F,a"), Opcode.SUBR to InstructionFormat.from("BW,<>r1,fr1,r1,fr1,a | F,a"), Opcode.MULR to InstructionFormat.from("BW,<>r1,fr1,r1,fr1,a | F,a"), Opcode.DIVR to InstructionFormat.from("BW,<>r1,r1,a"), Opcode.DIVSR to InstructionFormat.from("BW,<>r1,fr1,r1,fr1,a | F,a"), Opcode.SQRT to InstructionFormat.from("BW,>r1,fr1,r1,fr1,r1,r1,r1,r1,r1,r1,r1,r1,r1,r1,a"), Opcode.ORR to InstructionFormat.from("BW,<>r1,r1,a"), Opcode.XORR to InstructionFormat.from("BW,<>r1,r1,a"), Opcode.INV to InstructionFormat.from("BW,<>r1"), Opcode.INVM to InstructionFormat.from("BW,<>a"), Opcode.ASRN to InstructionFormat.from("BW,<>r1,a"), Opcode.LSRN to InstructionFormat.from("BW,<>r1,a"), Opcode.LSLN to InstructionFormat.from("BW,<>r1,a"), Opcode.ASR to InstructionFormat.from("BW,<>r1"), Opcode.ASRM to InstructionFormat.from("BW,<>a"), Opcode.LSR to InstructionFormat.from("BW,<>r1"), Opcode.LSRM to InstructionFormat.from("BW,<>a"), Opcode.LSL to InstructionFormat.from("BW,<>r1"), Opcode.LSLM to InstructionFormat.from("BW,<>a"), Opcode.ROR to InstructionFormat.from("BW,<>r1"), Opcode.RORM to InstructionFormat.from("BW,<>a"), Opcode.ROXR to InstructionFormat.from("BW,<>r1"), Opcode.ROXRM to InstructionFormat.from("BW,<>a"), Opcode.ROL to InstructionFormat.from("BW,<>r1"), Opcode.ROLM to InstructionFormat.from("BW,<>a"), Opcode.ROXL to InstructionFormat.from("BW,<>r1"), Opcode.ROXLM to InstructionFormat.from("BW,<>a"), Opcode.FFROMUB to InstructionFormat.from("F,>fr1,fr1,fr1,fr1,r1,r1,r1,r1,fr1,fr1,r1,fr1,fr1,fr1,fr1,fr1,fr1,fr1,fr1,fr1,r1,r1 | F,>fr1"), Opcode.PUSHST to InstructionFormat.from("N"), Opcode.POPST to InstructionFormat.from("N"), Opcode.CONCAT to InstructionFormat.from("BW,<>r1,, val returns: List ) { class RegSpec(val dt: IRDataType, val registerNum: Int, val cpuRegister: RegisterOrStatusflag?) class ArgumentSpec(val name: String, val address: Int?, val reg: RegSpec) { init { require(address==null || address>=0) { "address must be >=0" } } } } data class IRInstruction( val opcode: Opcode, val type: IRDataType?=null, val reg1: Int?=null, // 0-$ffff val reg2: Int?=null, // 0-$ffff val reg3: Int?=null, // 0-$ffff val fpReg1: Int?=null, // 0-$ffff val fpReg2: Int?=null, // 0-$ffff val immediate: Int?=null, // 0-$ff or $ffff if word val immediateFp: Double?=null, val address: Int?=null, // 0-$ffff val labelSymbol: String?=null, // symbolic label name as alternative to address (so only for Branch/jump/call Instructions!) private val symbolOffset: Int? = null, // offset to add on labelSymbol (used to index into an array variable) var branchTarget: IRCodeChunkBase? = null, // Will be linked after loading in IRProgram.linkChunks()! This is the chunk that the branch labelSymbol points to. val fcallArgs: FunctionCallArgs? = null // will be set for the CALL and SYSCALL instructions. ) { // reg1 and fpreg1 can be IN/OUT/INOUT (all others are readonly INPUT) // This knowledge is useful in IL assembly optimizers to see how registers are used. val reg1direction: OperandDirection val reg2direction: OperandDirection val reg3direction: OperandDirection val fpReg1direction: OperandDirection val fpReg2direction: OperandDirection val labelSymbolOffset = if(symbolOffset==0) null else symbolOffset init { if(labelSymbol!=null) { require(labelSymbol.first() != '_') { "label/symbol should not start with underscore $labelSymbol" } require(labelSymbol.all { it.isJavaIdentifierStart() || it.isJavaIdentifierPart() || it=='.' }) { "label/symbol contains invalid character $labelSymbol" } } if(labelSymbolOffset!=null) require(labelSymbolOffset>0 && labelSymbol!=null) {"labelsymbol offset inconsistency"} require(reg1==null || reg1 in 0..65536) {"reg1 out of bounds"} require(reg2==null || reg2 in 0..65536) {"reg2 out of bounds"} require(reg3==null || reg3 in 0..65536) {"reg3 out of bounds"} require(fpReg1==null || fpReg1 in 0..65536) {"fpReg1 out of bounds"} require(fpReg2==null || fpReg2 in 0..65536) {"fpReg2 out of bounds"} if(reg1!=null && reg2!=null) require(reg1!=reg2) {"reg1 must not be same as reg2"} // note: this is ok for fpRegs as these are always the same type if(reg1!=null && reg3!=null) require(reg1!=reg3) {"reg1 must not be same as reg3"} // note: this is ok for fpRegs as these are always the same type if(reg2!=null && reg3!=null) require(reg2!=reg3) {"reg2 must not be same as reg3"} // note: this is ok for fpRegs as these are always the same type val formats = instructionFormats.getValue(opcode) require (type != null || formats.containsKey(null)) { "missing type" } val format = formats.getOrElse(type) { throw IllegalArgumentException("type $type invalid for $opcode") } if(format.reg1!=OperandDirection.UNUSED) require(reg1!=null) { "missing reg1" } if(format.reg2!=OperandDirection.UNUSED) require(reg2!=null) { "missing reg2" } if(format.reg3!=OperandDirection.UNUSED) require(reg3!=null) { "missing reg3" } if(format.fpReg1!=OperandDirection.UNUSED) require(fpReg1!=null) { "missing fpReg1" } if(format.fpReg2!=OperandDirection.UNUSED) require(fpReg2!=null) { "missing fpReg2" } if(format.reg1==OperandDirection.UNUSED) require(reg1==null) { "invalid reg1" } if(format.reg2==OperandDirection.UNUSED) require(reg2==null) { "invalid reg2" } if(format.reg3==OperandDirection.UNUSED) require(reg3==null) { "invalid reg3" } if(format.fpReg1==OperandDirection.UNUSED) require(fpReg1==null) { "invalid fpReg1" } if(format.fpReg2==OperandDirection.UNUSED) require(fpReg2==null) { "invalid fpReg2" } if(format.immediate) { if(type==IRDataType.FLOAT) require(immediateFp !=null) {"missing immediate fp value"} else require(immediate!=null || labelSymbol!=null) {"missing immediate value or labelsymbol"} } if(type!=IRDataType.FLOAT) require(fpReg1==null && fpReg2==null) {"int instruction can't use fp reg"} if(format.address!=OperandDirection.UNUSED) require(address!=null || labelSymbol!=null) {"missing an address or labelsymbol"} if(format.immediate && (immediate!=null || immediateFp!=null)) { if(opcode!=Opcode.SYSCALL) { when (type) { IRDataType.BYTE -> require(immediate in -128..255) { "immediate value out of range for byte: $immediate" } IRDataType.WORD -> require(immediate in -32768..65535) { "immediate value out of range for word: $immediate" } IRDataType.FLOAT, null -> {} } } } if(format.immediate) { if(opcode==Opcode.LOAD) require(immediate != null || immediateFp != null || labelSymbol!=null) { "missing immediate value or labelsymbol" } else require(immediate != null || immediateFp != null) { "missing immediate value" } } require(address==null || address>=0) { "address must be >=0" } reg1direction = format.reg1 reg2direction = format.reg2 reg3direction = format.reg3 fpReg1direction = format.fpReg1 fpReg2direction = format.fpReg2 if(opcode==Opcode.SYSCALL) { require(immediate!=null) { "syscall needs immediate integer for the syscall number" } } } fun addUsedRegistersCounts( readRegsCounts: MutableMap, writeRegsCounts: MutableMap, readFpRegsCounts: MutableMap, writeFpRegsCounts: MutableMap, regsTypes: MutableMap> ) { when (this.reg1direction) { OperandDirection.UNUSED -> {} OperandDirection.READ -> { readRegsCounts[this.reg1!!] = readRegsCounts.getValue(this.reg1)+1 if(type!=null) { var types = regsTypes[this.reg1] if(types==null) types = mutableSetOf() types += type regsTypes[this.reg1] = types } } OperandDirection.WRITE -> { writeRegsCounts[this.reg1!!] = writeRegsCounts.getValue(this.reg1)+1 if(type!=null) { var types = regsTypes[this.reg1] if(types==null) types = mutableSetOf() types += type regsTypes[this.reg1] = types } } OperandDirection.READWRITE -> { readRegsCounts[this.reg1!!] = readRegsCounts.getValue(this.reg1)+1 if(type!=null) { var types = regsTypes[this.reg1] if(types==null) types = mutableSetOf() types += type regsTypes[this.reg1] = types } writeRegsCounts[this.reg1] = writeRegsCounts.getValue(this.reg1)+1 if(type!=null) { var types = regsTypes[this.reg1] if(types==null) types = mutableSetOf() types += type regsTypes[this.reg1] = types } } } when (this.reg2direction) { OperandDirection.UNUSED -> {} OperandDirection.READ -> { writeRegsCounts[this.reg2!!] = writeRegsCounts.getValue(this.reg2)+1 if(type!=null) { var types = regsTypes[this.reg2] if(types==null) types = mutableSetOf() types += type regsTypes[this.reg2] = types } } else -> throw IllegalArgumentException("reg2 can only be read") } when (this.reg3direction) { OperandDirection.UNUSED -> {} OperandDirection.READ -> { writeRegsCounts[this.reg3!!] = writeRegsCounts.getValue(this.reg3)+1 if(type!=null) { var types = regsTypes[this.reg3] if(types==null) types = mutableSetOf() types += type regsTypes[this.reg3] = types } } else -> throw IllegalArgumentException("reg3 can only be read") } when (this.fpReg1direction) { OperandDirection.UNUSED -> {} OperandDirection.READ -> { readFpRegsCounts[this.fpReg1!!] = readFpRegsCounts.getValue(this.fpReg1)+1 } OperandDirection.WRITE -> writeFpRegsCounts[this.fpReg1!!] = writeFpRegsCounts.getValue(this.fpReg1)+1 OperandDirection.READWRITE -> { readFpRegsCounts[this.fpReg1!!] = readFpRegsCounts.getValue(this.fpReg1)+1 writeFpRegsCounts[this.fpReg1] = writeFpRegsCounts.getValue(this.fpReg1)+1 } } when (this.fpReg2direction) { OperandDirection.UNUSED -> {} OperandDirection.READ -> readFpRegsCounts[this.fpReg2!!] = readFpRegsCounts.getValue(this.fpReg2)+1 else -> throw IllegalArgumentException("fpReg2 can only be read") } if(fcallArgs!=null) { fcallArgs.returns.forEach { if (it.dt == IRDataType.FLOAT) writeFpRegsCounts[it.registerNum] = writeFpRegsCounts.getValue(it.registerNum) + 1 else { writeRegsCounts[it.registerNum] = writeRegsCounts.getValue(it.registerNum) + 1 val types = regsTypes[it.registerNum] if (types == null) { regsTypes[it.registerNum] = mutableSetOf(it.dt) } else { types += it.dt regsTypes[it.registerNum] = types } } } fcallArgs.arguments.forEach { if(it.reg.dt==IRDataType.FLOAT) readFpRegsCounts[it.reg.registerNum] = readFpRegsCounts.getValue(it.reg.registerNum)+1 else { readRegsCounts[it.reg.registerNum] = readRegsCounts.getValue(it.reg.registerNum) + 1 val types = regsTypes[it.reg.registerNum] if(types==null) { regsTypes[it.reg.registerNum] = mutableSetOf(it.reg.dt) } else { types += it.reg.dt regsTypes[it.reg.registerNum] = types } } } } } override fun toString(): String { val result = mutableListOf(opcode.name.lowercase()) when(type) { IRDataType.BYTE -> result.add(".b ") IRDataType.WORD -> result.add(".w ") IRDataType.FLOAT -> result.add(".f ") else -> result.add(" ") } if(this.fcallArgs!=null) { immediate?.let { result.add(it.toHex()) } // syscall if(labelSymbol!=null) { // regular subroutine call result.add(labelSymbol) if(labelSymbolOffset!=null) result.add("+$labelSymbolOffset") } address?.let { result.add(address.toHex()) } // romcall result.add("(") fcallArgs.arguments.forEach { val location = if(it.address==null) { if(it.name.isBlank()) "" else it.name+"=" } else "${it.address}=" val cpuReg = if(it.reg.cpuRegister==null) "" else { if(it.reg.cpuRegister.registerOrPair!=null) "@"+it.reg.cpuRegister.registerOrPair.toString() else "@"+it.reg.cpuRegister.statusflag.toString() } when(it.reg.dt) { IRDataType.BYTE -> result.add("${location}r${it.reg.registerNum}.b$cpuReg,") IRDataType.WORD -> result.add("${location}r${it.reg.registerNum}.w$cpuReg,") IRDataType.FLOAT -> result.add("${location}fr${it.reg.registerNum}.f$cpuReg,") } } if(result.last().endsWith(',')) { result.add(result.removeLast().trimEnd(',')) } result.add(")") val returns = fcallArgs.returns if(returns.isNotEmpty()) { result.add(":") val resultParts = returns.map { returnspec -> val cpuReg = if (returnspec.cpuRegister == null) "" else { if (returnspec.cpuRegister.registerOrPair != null) returnspec.cpuRegister.registerOrPair.toString() else returnspec.cpuRegister.statusflag.toString() } if (cpuReg.isEmpty()) { when (returnspec.dt) { IRDataType.BYTE -> "r${returnspec.registerNum}.b" IRDataType.WORD -> "r${returnspec.registerNum}.w" IRDataType.FLOAT -> "fr${returnspec.registerNum}.f" } } else { when (returnspec.dt) { IRDataType.BYTE -> "r${returnspec.registerNum}.b@" + cpuReg IRDataType.WORD -> "r${returnspec.registerNum}.w@" + cpuReg IRDataType.FLOAT -> "r${returnspec.registerNum}.f@" + cpuReg } } } result.add(resultParts.joinToString(",")) } } else { reg1?.let { result.add("r$it") result.add(",") } reg2?.let { result.add("r$it") result.add(",") } reg3?.let { result.add("r$it") result.add(",") } fpReg1?.let { result.add("fr$it") result.add(",") } fpReg2?.let { result.add("fr$it") result.add(",") } immediate?.let { result.add(it.toHex()) result.add(",") } immediateFp?.let { result.add(it.toString()) result.add(",") } address?.let { result.add(it.toHex()) result.add(",") } labelSymbol?.let { result.add(it) if(labelSymbolOffset!=null) result.add("+$labelSymbolOffset") } } if(result.last() == ",") result.removeLast() return result.joinToString("").trimEnd() } }