/* Copyright © 2019 Michal Schulz https://github.com/michalsc This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "support.h" #include "M68k.h" #include "EmuFeatures.h" #include "cache.h" uint8_t SR_GetEALength(uint16_t *insn_stream, uint8_t ea, uint8_t imm_size) { uint8_t word_count = 0; uint8_t mode, reg; mode = (ea >> 3) & 7; reg = ea & 7; /* modes 0, 1, 2, 3 and 4 do not have extra words */ if (mode > 4) { if (mode == 5) /* 16-bit offset in next opcode */ word_count++; else if (mode == 6 || (mode == 7 && reg == 3)) { /* Reg- or PC-relative addressing mode */ uint16_t brief = cache_read_16(ICACHE, (uint32_t)(uintptr_t)&insn_stream[0]); /* Brief word is here */ word_count++; if (brief & 0x100) { /* Full brief format */ switch (brief & 3) { case 2: word_count++; /* Word outer displacement */ break; case 3: word_count += 2; /* Long outer displacement */ break; } switch (brief & 0x30) { case 0x20: word_count++; /* Word base displacement */ break; case 0x30: word_count += 2; /* Long base displacement */ break; } } } else if (mode == 7) { if (reg == 2) /* PC-relative with 16-bit offset in next opcode */ word_count++; else if (reg == 0) /* Absolute word */ word_count++; else if (reg == 1) /* Absolute long */ word_count += 2; else if (reg == 4) /* Immediate */ { switch (imm_size) { case 1: word_count++; break; case 2: word_count++; break; case 4: word_count+=2; break; case 8: word_count+=4; break; case 12: word_count+=6; default: break; } } } } return word_count; } /* Check if opcode is of branch kind or may result in a */ int M68K_IsBranch(uint16_t *insn_stream) { uint16_t opcode = cache_read_16(ICACHE, (uint32_t)(uintptr_t)&insn_stream[0]); if ( opcode == 0x007c || opcode == 0x027c || opcode == 0x0a7c || (opcode & 0xffc0) == 0x40c0 || (opcode & 0xffc0) == 0x46c0 || (opcode & 0xfff8) == 0x4848 || opcode == 0x4afc || (opcode & 0xfff0) == 0x4e40 || (opcode & 0xfff0) == 0x4e60 || opcode == 0x4e70 || opcode == 0x4e72 || opcode == 0x4e73 || opcode == 0x4e74 || opcode == 0x4e75 || opcode == 0x4e76 || opcode == 0x4e77 || (opcode & 0xfffe) == 0x4e7a || (opcode & 0xff80) == 0x4e80 || (opcode & 0xf0f8) == 0x50c8 || (opcode & 0xf000) == 0x6000 ) return 1; else return 0; } int M68K_GetMoveLength(uint16_t *insn_stream) { uint16_t opcode = cache_read_16(ICACHE, (uint32_t)(uintptr_t)&insn_stream[0]); int size = 0; int length = 1; uint8_t ea = opcode & 0x3f; if ((opcode & 0x3000) == 0x1000) size = 1; else if ((opcode & 0x3000) == 0x2000) size = 4; else size = 2; length += SR_GetEALength(&insn_stream[length], ea & 0x3f, size); ea = (opcode >> 3) & 0x38; ea |= (opcode >> 9) & 0x7; length += SR_GetEALength(&insn_stream[length], ea, size); return length; } int M68K_GetLineFLength(uint16_t *insn_stream) { uint16_t opcode = cache_read_16(ICACHE, (uint32_t)(uintptr_t)&insn_stream[0]); uint16_t opcode2 = cache_read_16(ICACHE, (uint32_t)(uintptr_t)&insn_stream[1]);; int length = 1; int need_ea = 0; int opsize = 0; /* MOVE16 (Ax)+, (Ay)+ */ if ((opcode & 0xfff8) == 0xf620 && (opcode2 & 0x8fff) == 0x8000) { length = 2; } /* MOVE16 other variations */ else if ((opcode & 0xffe0) == 0xf600) { length = 3; } /* CINV */ else if ((opcode & 0xff20) == 0xf400) { length = 1; } /* CPUSH */ else if ((opcode & 0xff20) == 0xf420) { length = 1; } /* FMOVECR reg */ else if (opcode == 0xf200 && (opcode2 & 0xfc00) == 0x5c00) { length = 2; } /* FABS */ else if ((opcode & 0xffc0) == 0xf200 && ((opcode2 & 0xa07f) == 0x0018 || (opcode2 & 0xa07b) == 0x0058)) { length = 2; if (opcode2 & 0x4000) { need_ea = 1; switch ((opcode2 >> 10) & 7) { case 0: case 1: opsize = 2; break; case 2: opsize = 6; break; case 3: // Packed!! opsize = 6; break; case 4: case 6: opsize = 1; break; case 5: opsize = 4; break; } } } /* FADD */ else if ((opcode & 0xffc0) == 0xf200 && ((opcode2 & 0xa07f) == 0x0022 || (opcode2 & 0xa07b) == 0x0062)) { length = 2; if (opcode2 & 0x4000) { need_ea = 1; switch ((opcode2 >> 10) & 7) { case 0: case 1: opsize = 2; break; case 2: opsize = 6; break; case 3: // Packed!! opsize = 6; break; case 4: case 6: opsize = 1; break; case 5: opsize = 4; break; } } } /* FBcc */ else if ((opcode & 0xff80) == 0xf280) { if (opcode & (1 << 6)) { length = 3; } else { length = 2; } } /* FCMP */ else if ((opcode & 0xffc0) == 0xf200 && (opcode2 & 0xa07f) == 0x0038) { length = 2; if (opcode2 & 0x4000) { need_ea = 1; switch ((opcode2 >> 10) & 7) { case 0: case 1: opsize = 2; break; case 2: opsize = 6; break; case 3: // Packed!! opsize = 6; break; case 4: case 6: opsize = 1; break; case 5: opsize = 4; break; } } } /* FDIV */ else if ((opcode & 0xffc0) == 0xf200 && ((opcode2 & 0xa07f) == 0x0020 || (opcode2 & 0xa07b) == 0x0060)) { length = 2; if (opcode2 & 0x4000) { need_ea = 1; switch ((opcode2 >> 10) & 7) { case 0: case 1: opsize = 2; break; case 2: opsize = 6; break; case 3: // Packed!! opsize = 6; break; case 4: case 6: opsize = 1; break; case 5: opsize = 4; break; } } } /* FINT */ else if ((opcode & 0xffc0) == 0xf200 && (opcode2 & 0xa07f) == 0x0001) { length = 2; if (opcode2 & 0x4000) { need_ea = 1; switch ((opcode2 >> 10) & 7) { case 0: case 1: opsize = 2; break; case 2: opsize = 6; break; case 3: // Packed!! opsize = 6; break; case 4: case 6: opsize = 1; break; case 5: opsize = 4; break; } } } /* FINTRZ */ else if ((opcode & 0xffc0) == 0xf200 && (opcode2 & 0xa07f) == 0x0003) { length = 2; if (opcode2 & 0x4000) { need_ea = 1; switch ((opcode2 >> 10) & 7) { case 0: case 1: opsize = 2; break; case 2: opsize = 6; break; case 3: // Packed!! opsize = 6; break; case 4: case 6: opsize = 1; break; case 5: opsize = 4; break; } } } /* FLOGN */ else if ((opcode & 0xffc0) == 0xf200 && (opcode2 & 0xa07f) == 0x0014) { length = 2; if (opcode2 & 0x4000) { need_ea = 1; switch ((opcode2 >> 10) & 7) { case 0: case 1: opsize = 2; break; case 2: opsize = 6; break; case 3: // Packed!! opsize = 6; break; case 4: case 6: opsize = 1; break; case 5: opsize = 4; break; } } } /* FMOVE to REG */ else if ((opcode & 0xffc0) == 0xf200 && ((opcode2 & 0xa07f) == 0x0000 || (opcode2 & 0xa07b) == 0x0040)) { length = 2; if (opcode2 & 0x4000) { need_ea = 1; switch ((opcode2 >> 10) & 7) { case 0: case 1: opsize = 2; break; case 2: opsize = 6; break; case 3: // Packed!! opsize = 6; break; case 4: case 6: opsize = 1; break; case 5: opsize = 4; break; } } } /* FMOVE to MEM */ else if ((opcode & 0xffc0) == 0xf200 && (opcode2 & 0xe07f) == 0x6000) { length = 2; need_ea = 1; switch ((opcode2 >> 10) & 7) { case 0: case 1: opsize = 2; break; case 2: opsize = 6; break; case 3: // Packed!! case 7: opsize = 6; break; case 4: case 6: opsize = 1; break; case 5: opsize = 4; break; } } /* FMOVE from special */ else if ((opcode & 0xffc0) == 0xf200 && (opcode2 & 0xe3ff) == 0xa000) { length = 2; need_ea = 1; opsize = 2; } /* FMOVE to special */ else if ((opcode & 0xffc0) == 0xf200 && (opcode2 & 0xe3ff) == 0x8000) { length = 2; need_ea = 1; opsize = 2; } /* FMOVEM */ else if ((opcode & 0xffc0) == 0xf200 && (opcode2 & 0xc700) == 0xc000) { length = 2; need_ea = 1; } /* FMUL */ else if ((opcode & 0xffc0) == 0xf200 && ((opcode2 & 0xa07f) == 0x0023 || (opcode2 & 0xa07b) == 0x0063)) { length = 2; if (opcode2 & 0x4000) { need_ea = 1; switch ((opcode2 >> 10) & 7) { case 0: case 1: opsize = 2; break; case 2: opsize = 6; break; case 3: // Packed!! opsize = 6; break; case 4: case 6: opsize = 1; break; case 5: opsize = 4; break; } } } /* FNEG */ else if ((opcode & 0xffc0) == 0xf200 && ((opcode2 & 0xa07f) == 0x001a || (opcode2 & 0xa07b) == 0x005a)) { length = 2; if (opcode2 & 0x4000) { need_ea = 1; switch ((opcode2 >> 10) & 7) { case 0: case 1: opsize = 2; break; case 2: opsize = 6; break; case 3: // Packed!! opsize = 6; break; case 4: case 6: opsize = 1; break; case 5: opsize = 4; break; } } } /* FTST */ else if ((opcode & 0xffc0) == 0xf200 && (opcode2 & 0xa07f) == 0x003a) { length = 2; if (opcode2 & 0x4000) { need_ea = 1; switch ((opcode2 >> 10) & 7) { case 0: case 1: opsize = 2; break; case 2: opsize = 6; break; case 3: // Packed!! opsize = 6; break; case 4: case 6: opsize = 1; break; case 5: opsize = 4; break; } } } /* FScc */ else if ((opcode & 0xffc0) == 0xf240 && (opcode2 & 0xffc0) == 0) { need_ea = 1; opsize = 1; length = 2; } /* FSQRT */ else if ((opcode & 0xffc0) == 0xf200 && ((opcode2 & 0xa07f) == 0x0004 || (opcode2 & 0xa07b) == 0x0041)) { length = 2; if (opcode2 & 0x4000) { need_ea = 1; switch ((opcode2 >> 10) & 7) { case 0: case 1: opsize = 2; break; case 2: opsize = 6; break; case 3: // Packed!! opsize = 6; break; case 4: case 6: opsize = 1; break; case 5: opsize = 4; break; } } } /* FSUB */ else if ((opcode & 0xffc0) == 0xf200 && ((opcode2 & 0xa07f) == 0x0028 || (opcode2 & 0xa07b) == 0x0068)) { length = 2; if (opcode2 & 0x4000) { need_ea = 1; switch ((opcode2 >> 10) & 7) { case 0: case 1: opsize = 2; break; case 2: opsize = 6; break; case 3: // Packed!! opsize = 6; break; case 4: case 6: opsize = 1; break; case 5: opsize = 4; break; } } } /* FSIN */ else if ((opcode & 0xffc0) == 0xf200 && (opcode2 & 0xa07f) == 0x000e) { length = 2; if (opcode2 & 0x4000) { need_ea = 1; switch ((opcode2 >> 10) & 7) { case 0: case 1: opsize = 2; break; case 2: opsize = 6; break; case 3: // Packed!! opsize = 6; break; case 4: case 6: opsize = 1; break; case 5: opsize = 4; break; } } } /* FCOS */ else if ((opcode & 0xffc0) == 0xf200 && (opcode2 & 0xa07f) == 0x001d) { length = 2; if (opcode2 & 0x4000) { need_ea = 1; switch ((opcode2 >> 10) & 7) { case 0: case 1: opsize = 2; break; case 2: opsize = 6; break; case 3: // Packed!! opsize = 6; break; case 4: case 6: opsize = 1; break; case 5: opsize = 4; break; } } } /* FNOP */ else if (opcode == 0xf280 && opcode2 == 0) { length = 2; } else { length = 2; } if (need_ea) { length += SR_GetEALength(&insn_stream[length], opcode & 0x3f, opsize * 2); } kprintf("GetLineFLength for opcode %04x returns %d\n", opcode, 2*length); return length; } /* Get number of 16-bit words this instruction occupies */ int M68K_GetINSNLength(uint16_t *insn_stream) { uint16_t opcode = cache_read_16(ICACHE, (uint32_t)(uintptr_t)&insn_stream[0]); int length = 0; switch(opcode & 0xf000) { case 0x0000: length = M68K_GetLine0Length(insn_stream); break; case 0x1000: /* Fallthrough */ case 0x2000: /* Fallthrough */ case 0x3000: length = M68K_GetMoveLength(insn_stream); break; case 0x4000: length = M68K_GetLine4Length(insn_stream); break; case 0x5000: length = M68K_GetLine5Length(insn_stream); break; case 0x6000: length = M68K_GetLine6Length(insn_stream); break; case 0x7000: length = 1; break; case 0x8000: length = M68K_GetLine8Length(insn_stream); break; case 0x9000: length = M68K_GetLine9Length(insn_stream); break; case 0xa000: length = 1; break; case 0xb000: length = M68K_GetLineBLength(insn_stream); break; case 0xc000: length = M68K_GetLineCLength(insn_stream); break; case 0xd000: length = M68K_GetLineDLength(insn_stream); break; case 0xe000: length = M68K_GetLineELength(insn_stream); break; case 0xf000: length = M68K_GetLineFLength(insn_stream); break; default: break; } return length; } #define D(x) /* x */ typedef uint32_t (*SR_Check)(uint16_t opcode); static uint32_t GetSR_def(uint16_t opcode) { (void)opcode; return (SR_CCR << 16) | SR_CCR; } static SR_Check SRCheck[16] = { GetSR_Line0, GetSR_Line1, GetSR_Line2, GetSR_Line3, GetSR_Line4, GetSR_Line5, GetSR_Line6, GetSR_Line7, GetSR_Line8, GetSR_Line9, GetSR_def, GetSR_LineB, GetSR_LineC, GetSR_LineD, GetSR_LineE, GetSR_def }; extern struct M68KState *__m68k_state; /* Get the mask of status flags changed by the instruction specified by the opcode */ uint8_t M68K_GetSRMask(uint16_t *insn_stream) { uint16_t opcode = cache_read_16(ICACHE, (uint32_t)(uintptr_t)insn_stream); int scan_depth = 0; const int max_scan_depth = (__m68k_state->JIT_CONTROL2 >> JC2B_CCR_SCAN_DEPTH) & JC2_CCR_SCAN_MASK; uint8_t mask = 0; uint8_t needed = 0; uint8_t tmp_sets = 0; uint8_t tmp_needs = 0; D(kprintf("[JIT] GetSRMask, opcode %04x @ %08x, ", opcode, insn_stream)); uint32_t flags = SRCheck[opcode >> 12](opcode); mask = flags & 0x1f; tmp_sets = flags & 0x1f; tmp_needs = (flags >> 16) & 0x1f; D(kprintf(" SRNeeds = %x, SRSets = %x\n", tmp_needs, tmp_sets)); /* Check as long as there are still some flags to be set by the opcode and the depth of scan is not exceeded */ while(mask != 0 && scan_depth < max_scan_depth) { /* Increase scan depth level */ scan_depth++; /* If instruction is a branch break the scan */ if (M68K_IsBranch(insn_stream)) { /* Check if BRA/BSR and follow if possible */ if ((opcode & 0xfe00) == 0x6000) { int32_t branch_offset = (int8_t)(opcode & 0xff); if ((opcode & 0xff) == 0) { branch_offset = (int16_t)cache_read_16(ICACHE, (uint32_t)(uintptr_t)&insn_stream[1]); } else if ((opcode & 0xff) == 0xff) { uint16_t lo16, hi16; hi16 = cache_read_16(ICACHE, (uint32_t)(uintptr_t)&insn_stream[1]); lo16 = cache_read_16(ICACHE, (uint32_t)(uintptr_t)&insn_stream[2]); branch_offset = lo16 | (hi16 << 16); } insn_stream = insn_stream + 1 + (branch_offset >> 1); D(kprintf("[JIT] %02d: PC-relative jump by %d bytes to %08x\n", scan_depth, branch_offset, insn_stream)); } /* Check if JMP/JSR and follow if possible */ else if ((opcode & 0xffbe) == 0x4eb8) { if (opcode & 1) { uint16_t lo16, hi16; hi16 = cache_read_16(ICACHE, (uint32_t)(uintptr_t)&insn_stream[1]); lo16 = cache_read_16(ICACHE, (uint32_t)(uintptr_t)&insn_stream[2]); insn_stream = (uint16_t*)(uintptr_t)(lo16 | (hi16 << 16)); } else { insn_stream = (uint16_t*)(uintptr_t)((uint32_t)cache_read_16(ICACHE, (uint32_t)(uintptr_t)&insn_stream[1])); } D(kprintf("[JIT] %02d: Absolute jump to %08x\n", scan_depth, insn_stream)); } else if ((opcode & 0xf000) == 0x6000) { int32_t branch_offset = (int8_t)(opcode & 0xff); uint16_t *insn_stream_2 = insn_stream + 1; // Mark the flags which conditional jump needs by itself needed |= mask & (SRCheck[opcode >> 12](opcode) >> 16); if ((opcode & 0xff) == 0) { branch_offset = (int16_t)cache_read_16(ICACHE, (uint32_t)(uintptr_t)&insn_stream[1]); insn_stream_2++; } else if ((opcode & 0xff) == 0xff) { uint16_t lo16, hi16; hi16 = cache_read_16(ICACHE, (uint32_t)(uintptr_t)&insn_stream[1]); lo16 = cache_read_16(ICACHE, (uint32_t)(uintptr_t)&insn_stream[2]); branch_offset = lo16 | (hi16 << 16); insn_stream_2+=2; } insn_stream = insn_stream + 1 + (branch_offset >> 1); D(kprintf("[JIT] %02d: Splitting into two paths %08x and %08x\n", scan_depth, insn_stream, insn_stream_2)); uint8_t mask1 = mask; uint8_t mask2 = mask; uint8_t needed1 = needed; uint8_t needed2 = needed; int scan_depth_tmp = scan_depth; while(mask1 && scan_depth < max_scan_depth) { scan_depth++; /* If instruction is a branch break the scan */ if (M68K_IsBranch(insn_stream)) break; /* Get opcode */ opcode = cache_read_16(ICACHE, (uint32_t)(uintptr_t)insn_stream); D(kprintf("[JIT] %02d.1: opcode=%04x @ %08x ", scan_depth, opcode, insn_stream)); uint32_t flags = SRCheck[opcode >> 12](opcode); tmp_sets = flags & 0x1f; tmp_needs = (flags >> 16) & 0x1f; D(kprintf(" SRNeeds = %x, SRSets = %x\n", tmp_needs, tmp_sets)); /* If instruction *needs* one of flags from current opcode, break the check and return mask */ if (mask1 & tmp_needs) { needed1 |= (mask1 & tmp_needs); } /* Clear flags which this instruction sets */ mask1 = mask1 & ~tmp_sets; if ((mask1 & tmp_needs) == mask1) { break; } /* Advance to subsequent instruction */ insn_stream += M68K_GetINSNLength(insn_stream); } scan_depth = scan_depth_tmp; while(mask2 && scan_depth < max_scan_depth) { scan_depth++; /* If instruction is a branch break the scan */ if (M68K_IsBranch(insn_stream_2)) break; /* Get opcode */ opcode = cache_read_16(ICACHE, (uint32_t)(uintptr_t)insn_stream_2); D(kprintf("[JIT] %02d.2: opcode=%04x @ %08x ", scan_depth, opcode, insn_stream_2)); uint32_t flags = SRCheck[opcode >> 12](opcode); tmp_sets = flags & 0x1f; tmp_needs = (flags >> 16) & 0x1f; D(kprintf(" SRNeeds = %x, SRSets = %x\n", tmp_needs, tmp_sets)); /* If instruction *needs* one of flags from current opcode, break the check and return mask */ if (mask2 & tmp_needs) { needed2 |= (mask2 & tmp_needs); } /* Clear flags which this instruction sets */ mask2 = mask2 & ~tmp_sets; if ((mask2 & tmp_needs) == mask2) { break; } /* Advance to subsequent instruction */ insn_stream_2 += M68K_GetINSNLength(insn_stream_2); } D(kprintf("[JIT] joining masks %x and %x to %x\n", mask1 | needed1, mask2 | needed2, mask1 | needed1 | mask2 | needed2)); return mask1 | needed1 | mask2 | needed2; } else { D(kprintf("[JIT] %02d: check breaks on branch\n", scan_depth)); break; } } else { /* Advance to subsequent instruction */ insn_stream += M68K_GetINSNLength(insn_stream); } /* Get opcode */ opcode = cache_read_16(ICACHE, (uint32_t)(uintptr_t)insn_stream); D(kprintf("[JIT] %02d: opcode=%04x @ %08x ", scan_depth, opcode, insn_stream)); uint32_t flags = SRCheck[opcode >> 12](opcode); tmp_sets = flags & 0x1f; tmp_needs = (flags >> 16) & 0x1f; D(kprintf(" SRNeeds = %x, SRSets = %x\n", tmp_needs, tmp_sets)); /* If instruction *needs* one of flags from current opcode, break the check and return mask */ if (mask & tmp_needs) { needed |= (mask & tmp_needs); } /* Clear flags which this instruction sets */ mask = mask & ~tmp_sets; if ((mask & tmp_needs) == mask) { break; } } D(kprintf("[JIT] GetSRMask returns %x\n", mask | needed)); return mask | needed; }