/*
    Copyright © 2019 Michal Schulz <michal.schulz@gmx.de>
    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 "RegisterAllocator.h"
#include "cache.h"

uint32_t *EMIT_ADDQ(uint32_t *ptr, uint16_t opcode, uint16_t **m68k_ptr)
{
    uint8_t update_mask = M68K_GetSRMask(*m68k_ptr - 1);
    
    /* ADDQ */
    uint8_t update_cc = 1;
    uint8_t ext_count = 0;
    uint8_t data = (opcode >> 9) & 7;
    if (data == 0) data = 8;

    if ((opcode & 0x30) == 0)
    {
        /* Dx or Ax case */
        uint8_t dx = (opcode & 0x38) == 0;
        uint8_t tmp;

        if (dx)
        {
            /* Fetch m68k register */
            uint8_t dest = RA_MapM68kRegister(&ptr, (opcode & 7) + (dx ? 0 : 8));
            RA_SetDirtyM68kRegister(&ptr, (opcode & 7) + (dx ? 0 : 8));

            switch ((opcode >> 6) & 3)
            {
            case 0:
                tmp = RA_AllocARMRegister(&ptr);

                if (update_mask == 0 || update_mask == SR_Z || update_mask == SR_N) {
                    *ptr++ = add_immed(tmp, dest, data);
                    *ptr++ = bfxil(dest, tmp, 0, 8);

                    if (update_mask == SR_Z) {
                        *ptr++ = ands_immed(31, tmp, 8, 0);
                    }
                    else if (update_mask == SR_N) {
                        *ptr++ = ands_immed(31, tmp, 1, 32-7);
                    }
                }
                else 
                {
                    *ptr++ = mov_immed_u16(tmp, data << 8, 1);
                    *ptr++ = adds_reg(tmp, tmp, dest, LSL, 24);
                    *ptr++ = bfxil(dest, tmp, 24, 8);
                }

                RA_FreeARMRegister(&ptr, tmp);
                break;

            case 1:
                tmp = RA_AllocARMRegister(&ptr);

                if (update_mask == 0 || update_mask == SR_Z || update_mask == SR_N) {
                    *ptr++ = add_immed(tmp, dest, data);
                    *ptr++ = bfxil(dest, tmp, 0, 16);

                    if (update_mask == SR_Z) {
                        *ptr++ = ands_immed(31, tmp, 16, 0);
                    }
                    else if (update_mask == SR_N) {
                        *ptr++ = ands_immed(31, tmp, 1, 32-15);
                    }
                }
                else 
                {
                    *ptr++ = mov_immed_u16(tmp, data, 1);
                    *ptr++ = adds_reg(tmp, tmp, dest, LSL, 16);
                    *ptr++ = bfxil(dest, tmp, 16, 16);
                }

                RA_FreeARMRegister(&ptr, tmp);
                break;

            case 2:
                *ptr++ = adds_immed(dest, dest, data);
                break;
            }
        }
        else
        {
            /* Fetch m68k register */
            uint8_t dest = RA_MapM68kRegister(&ptr, (opcode & 7) + 8);
            RA_SetDirtyM68kRegister(&ptr, (opcode & 7) + 8);

            update_cc = 0;

            *ptr++ = add_immed(dest, dest, data);
        }
    }
    else
    {
        /* Load effective address */
        uint8_t dest = 0xff;
        uint8_t tmp = RA_AllocARMRegister(&ptr);
        uint8_t mode = (opcode & 0x0038) >> 3;

        if (mode == 4 || mode == 3)
            ptr = EMIT_LoadFromEffectiveAddress(ptr, 0, &dest, opcode & 0x3f, *m68k_ptr, &ext_count, 0, NULL);
        else
            ptr = EMIT_LoadFromEffectiveAddress(ptr, 0, &dest, opcode & 0x3f, *m68k_ptr, &ext_count, 1, NULL);

        switch ((opcode >> 6) & 3)
        {
            case 0: /* 8-bit */
                if (mode == 4)
                {
                    *ptr++ = ldrb_offset_preindex(dest, tmp, (opcode & 7) == 7 ? -2 : -1);
                    RA_SetDirtyM68kRegister(&ptr, 8 + (opcode & 7));
                }
                else
                    *ptr++ = ldrb_offset(dest, tmp, 0);
                
                /* Perform calcualtion */
                if (update_mask == 0 || update_mask == SR_Z || update_mask == SR_N) {
                    *ptr++ = add_immed(tmp, tmp, data);
                    
                    if (update_mask == SR_Z) {
                        *ptr++ = ands_immed(31, tmp, 8, 0);
                    }
                    else if (update_mask == SR_N) {
                        *ptr++ = ands_immed(31, tmp, 1, 32-7);
                    }
                }
                else 
                {
                    uint8_t immed = RA_AllocARMRegister(&ptr);
                    *ptr++ = mov_immed_u16(immed, data << 8, 1);
                    *ptr++ = adds_reg(tmp, immed, tmp, LSL, 24);
                    *ptr++ = lsr(tmp, tmp, 24);
                    RA_FreeARMRegister(&ptr, immed);
                }

                /* Store back */
                if (mode == 3)
                {
                    *ptr++ = strb_offset_postindex(dest, tmp, (opcode & 7) == 7 ? 2 : 1);
                    RA_SetDirtyM68kRegister(&ptr, 8 + (opcode & 7));
                }
                else
                    *ptr++ = strb_offset(dest, tmp, 0);
                break;
            
            case 1: /* 16-bit */
                if (mode == 4)
                {
                    *ptr++ = ldrh_offset_preindex(dest, tmp, -2);
                    RA_SetDirtyM68kRegister(&ptr, 8 + (opcode & 7));
                }
                else
                    *ptr++ = ldrh_offset(dest, tmp, 0);

                /* Perform calcualtion */
                if (update_mask == 0 || update_mask == SR_Z || update_mask == SR_N) {
                    *ptr++ = add_immed(tmp, tmp, data);

                    if (update_mask == SR_Z) {
                        *ptr++ = ands_immed(31, tmp, 16, 0);
                    }
                    else if (update_mask == SR_N) {
                        *ptr++ = ands_immed(31, tmp, 1, 32-15);
                    }
                }
                else 
                {
                    uint8_t immed = RA_AllocARMRegister(&ptr);
                    *ptr++ = mov_immed_u16(immed, data, 1);
                    *ptr++ = adds_reg(tmp, immed, tmp, LSL, 16);
                    *ptr++ = lsr(tmp, tmp, 16);
                    RA_FreeARMRegister(&ptr, immed);
                }

                /* Store back */
                if (mode == 3)
                {
                    *ptr++ = strh_offset_postindex(dest, tmp, 2);
                    RA_SetDirtyM68kRegister(&ptr, 8 + (opcode & 7));
                }
                else
                    *ptr++ = strh_offset(dest, tmp, 0);
                break;

            case 2: /* 32-bit */
                if (mode == 4)
                {
                    *ptr++ = ldr_offset_preindex(dest, tmp, -4);
                    RA_SetDirtyM68kRegister(&ptr, 8 + (opcode & 7));
                }
                else
                    *ptr++ = ldr_offset(dest, tmp, 0);

                /* Perform calcualtion */
                *ptr++ = adds_immed(tmp, tmp, data);

                /* Store back */
                if (mode == 3)
                {
                    *ptr++ = str_offset_postindex(dest, tmp, 4);
                    RA_SetDirtyM68kRegister(&ptr, 8 + (opcode & 7));
                }
                else
                    *ptr++ = str_offset(dest, tmp, 0);
                break;
        }

        RA_FreeARMRegister(&ptr, dest);
        RA_FreeARMRegister(&ptr, tmp);
    }

    if (((opcode >> 6) & 3) < 2)
    {
        if (update_mask == SR_Z) 
        {
            uint8_t cc = RA_ModifyCC(&ptr);
            ptr = EMIT_ClearFlags(ptr, cc, SR_Z);          
            ptr = EMIT_SetFlagsConditional(ptr, cc, SR_Z, A64_CC_EQ);
            update_mask = 0;
        }
        else if (update_mask == SR_N) 
        {
            uint8_t cc = RA_ModifyCC(&ptr);
            ptr = EMIT_ClearFlags(ptr, cc, SR_N);          
            ptr = EMIT_SetFlagsConditional(ptr, cc, SR_N, A64_CC_NE);
            update_mask = 0;
        }
    }

    ptr = EMIT_AdvancePC(ptr, 2 * (ext_count + 1));

    (*m68k_ptr) += ext_count;

    if (update_cc)
    {
        if (update_mask)
        {
            uint8_t cc = RA_ModifyCC(&ptr);
            if (update_mask & SR_X)
                ptr = EMIT_GetNZCVX(ptr, cc, &update_mask);
            else
                ptr = EMIT_GetNZCV(ptr, cc, &update_mask);

            if (update_mask & SR_Z)
                ptr = EMIT_SetFlagsConditional(ptr, cc, SR_Z, ARM_CC_EQ);
            if (update_mask & SR_N)
                ptr = EMIT_SetFlagsConditional(ptr, cc, SR_N, ARM_CC_MI);
            if (update_mask & SR_V)
                ptr = EMIT_SetFlagsConditional(ptr, cc, SR_Valt, ARM_CC_VS);
            if (update_mask & (SR_X | SR_C)) {
                if ((update_mask & (SR_X | SR_C)) == SR_X)
                    ptr = EMIT_SetFlagsConditional(ptr, cc, SR_X, ARM_CC_CS);
                else if ((update_mask & (SR_X | SR_C)) == SR_C)
                    ptr = EMIT_SetFlagsConditional(ptr, cc, SR_Calt, ARM_CC_CS);
                else
                    ptr = EMIT_SetFlagsConditional(ptr, cc, SR_Calt | SR_X, ARM_CC_CS);
            }
        }
    }

    return ptr;
}

uint32_t *EMIT_SUBQ(uint32_t *ptr, uint16_t opcode, uint16_t **m68k_ptr)
{
    uint8_t update_mask = M68K_GetSRMask(*m68k_ptr - 1);

    /* SUBQ */
    uint8_t update_cc = 1;
    uint8_t ext_count = 0;
    uint8_t data = (opcode >> 9) & 7;
    if (data == 0)
        data = 8;

    if ((opcode & 0x30) == 0)
    {
        /* Dx or Ax case */
        uint8_t dx = (opcode & 0x38) == 0;
        uint8_t tmp;

        if (dx)
        {
            /* Fetch m68k register */
            uint8_t dest = RA_MapM68kRegister(&ptr, (opcode & 7));
            RA_SetDirtyM68kRegister(&ptr, (opcode & 7));
            uint8_t tmp2 = RA_AllocARMRegister(&ptr);

            switch ((opcode >> 6) & 3)
            {
            case 0:
                tmp = RA_AllocARMRegister(&ptr);
                if (update_mask == 0 || update_mask == SR_Z || update_mask == SR_N) {
                    *ptr++ = sub_immed(tmp, dest, data);
                    *ptr++ = bfxil(dest, tmp, 0, 8);

                    if (update_mask == SR_Z) {
                        *ptr++ = ands_immed(31, tmp, 8, 0);
                    }
                    else if (update_mask == SR_N) {
                        *ptr++ = ands_immed(31, tmp, 1, 32-7);
                    }
                }
                else
                {
                    *ptr++ = lsl(tmp2, dest, 24);
                    *ptr++ = mov_immed_u16(tmp, data << 8, 1);
                    *ptr++ = subs_reg(tmp, tmp2, tmp, LSL, 0);
                    *ptr++ = bfxil(dest, tmp, 24, 8);
                }

                RA_FreeARMRegister(&ptr, tmp);
                break;

            case 1:
                tmp = RA_AllocARMRegister(&ptr);
                if (update_mask == 0 || update_mask == SR_Z || update_mask == SR_N) {
                    *ptr++ = sub_immed(tmp, dest, data);
                    *ptr++ = bfxil(dest, tmp, 0, 16);

                    if (update_mask == SR_Z) {
                        *ptr++ = ands_immed(31, tmp, 16, 0);
                    }
                    else if (update_mask == SR_N) {
                        *ptr++ = ands_immed(31, tmp, 1, 32-15);
                    }
                }
                else
                {
                    *ptr++ = lsl(tmp2, dest, 16);
                    *ptr++ = mov_immed_u16(tmp, data, 1);
                    *ptr++ = subs_reg(tmp, tmp2, tmp, LSL, 0);
                    *ptr++ = bfxil(dest, tmp, 16, 16);
                }

                RA_FreeARMRegister(&ptr, tmp);
                break;

            case 2:
                *ptr++ = subs_immed(dest, dest, data);
                break;
            }

            RA_FreeARMRegister(&ptr, tmp2);
        }
        else
        {
            /* Fetch m68k register */
            uint8_t dest = RA_MapM68kRegister(&ptr, (opcode & 7) + 8);
            RA_SetDirtyM68kRegister(&ptr, (opcode & 7) + 8);

            update_cc = 0;

            *ptr++ = subs_immed(dest, dest, data);
        }
    }
    else
    {
        /* Load effective address */
        uint8_t dest = 0xff;
        uint8_t tmp = RA_AllocARMRegister(&ptr);
        uint8_t mode = (opcode & 0x0038) >> 3;

        if (mode == 4 || mode == 3)
            ptr = EMIT_LoadFromEffectiveAddress(ptr, 0, &dest, opcode & 0x3f, *m68k_ptr, &ext_count, 0, NULL);
        else
            ptr = EMIT_LoadFromEffectiveAddress(ptr, 0, &dest, opcode & 0x3f, *m68k_ptr, &ext_count, 1, NULL);

        switch ((opcode >> 6) & 3)
        {
        case 0: /* 8-bit */
            if (mode == 4)
            {
                *ptr++ = ldrb_offset_preindex(dest, tmp, (opcode & 7) == 7 ? -2 : -1);
                RA_SetDirtyM68kRegister(&ptr, 8 + (opcode & 7));
            }
            else
                *ptr++ = ldrb_offset(dest, tmp, 0);
            
            /* Perform calcualtion */
            if (update_mask == 0 || update_mask == SR_Z || update_mask == SR_N) {
                *ptr++ = sub_immed(tmp, tmp, data);

                if (update_mask == SR_Z) {
                    *ptr++ = ands_immed(31, tmp, 8, 0);
                }
                else if (update_mask == SR_N) {
                    *ptr++ = ands_immed(31, tmp, 1, 32-7);
                }
            }
            else
            {
                uint8_t immed = RA_AllocARMRegister(&ptr);
                *ptr++ = lsl(tmp, tmp, 24);
                *ptr++ = mov_immed_u16(immed, data << 8, 1);
                *ptr++ = subs_reg(tmp, tmp, immed, LSL, 0);
                *ptr++ = lsr(tmp, tmp, 24);
                RA_FreeARMRegister(&ptr, immed);
            }

            /* Store back */
            if (mode == 3)
            {
                *ptr++ = strb_offset_postindex(dest, tmp, (opcode & 7) == 7 ? 2 : 1);
                RA_SetDirtyM68kRegister(&ptr, 8 + (opcode & 7));
            }
            else
                *ptr++ = strb_offset(dest, tmp, 0);
            break;
        
        case 1: /* 16-bit */
            if (mode == 4)
            {
                *ptr++ = ldrh_offset_preindex(dest, tmp, -2);
                RA_SetDirtyM68kRegister(&ptr, 8 + (opcode & 7));
            }
            else
                *ptr++ = ldrh_offset(dest, tmp, 0);

            /* Perform calcualtion */
            if (update_mask == 0 || update_mask == SR_Z || update_mask == SR_N) {
                *ptr++ = sub_immed(tmp, tmp, data);

                if (update_mask == SR_Z) {
                    *ptr++ = ands_immed(31, tmp, 16, 0);
                }
                else if (update_mask == SR_N) {
                    *ptr++ = ands_immed(31, tmp, 1, 32-15);
                }
            }
            else
            {
                uint8_t immed = RA_AllocARMRegister(&ptr);
                *ptr++ = lsl(tmp, tmp, 16);
                *ptr++ = mov_immed_u16(immed, data, 1);
                *ptr++ = subs_reg(tmp, tmp, immed, LSL, 0);
                *ptr++ = lsr(tmp, tmp, 16);
                RA_FreeARMRegister(&ptr, immed);
            }

            /* Store back */
            if (mode == 3)
            {
                *ptr++ = strh_offset_postindex(dest, tmp, 2);
                RA_SetDirtyM68kRegister(&ptr, 8 + (opcode & 7));
            }
            else
                *ptr++ = strh_offset(dest, tmp, 0);
            break;

        case 2: /* 32-bit */
            if (mode == 4)
            {
                *ptr++ = ldr_offset_preindex(dest, tmp, -4);
                RA_SetDirtyM68kRegister(&ptr, 8 + (opcode & 7));
            }
            else
                *ptr++ = ldr_offset(dest, tmp, 0);

            /* Perform calcualtion */
            *ptr++ = subs_immed(tmp, tmp, data);

            /* Store back */
            if (mode == 3)
            {
                *ptr++ = str_offset_postindex(dest, tmp, 4);
                RA_SetDirtyM68kRegister(&ptr, 8 + (opcode & 7));
            }
            else
                *ptr++ = str_offset(dest, tmp, 0);
            break;
        }

        RA_FreeARMRegister(&ptr, dest);
        RA_FreeARMRegister(&ptr, tmp);
    }

    if (((opcode >> 6) & 3) < 2)
    {
        if (update_mask == SR_Z) 
        {
            uint8_t cc = RA_ModifyCC(&ptr);
            ptr = EMIT_ClearFlags(ptr, cc, SR_Z);          
            ptr = EMIT_SetFlagsConditional(ptr, cc, SR_Z, A64_CC_EQ);
            update_mask = 0;
        }
        else if (update_mask == SR_N) 
        {
            uint8_t cc = RA_ModifyCC(&ptr);
            ptr = EMIT_ClearFlags(ptr, cc, SR_N);          
            ptr = EMIT_SetFlagsConditional(ptr, cc, SR_N, A64_CC_NE);
            update_mask = 0;
        }
    }

    ptr = EMIT_AdvancePC(ptr, 2 * (ext_count + 1));

    (*m68k_ptr) += ext_count;

    if (update_cc)
    {
        if (update_mask)
        {
            uint8_t cc = RA_ModifyCC(&ptr);
            if (update_mask & SR_X)
                ptr = EMIT_GetNZnCVX(ptr, cc, &update_mask);
            else
                ptr = EMIT_GetNZnCV(ptr, cc, &update_mask);

            if (update_mask & SR_Z)
                ptr = EMIT_SetFlagsConditional(ptr, cc, SR_Z, ARM_CC_EQ);
            if (update_mask & SR_N)
                ptr = EMIT_SetFlagsConditional(ptr, cc, SR_N, ARM_CC_MI);
            if (update_mask & SR_V)
                ptr = EMIT_SetFlagsConditional(ptr, cc, SR_Valt, ARM_CC_VS);
            if (update_mask & (SR_X | SR_C)) {
                if ((update_mask & (SR_X | SR_C)) == SR_X)
                    ptr = EMIT_SetFlagsConditional(ptr, cc, SR_X, ARM_CC_CC);
                else if ((update_mask & (SR_X | SR_C)) == SR_C)
                    ptr = EMIT_SetFlagsConditional(ptr, cc, SR_Calt, ARM_CC_CC);
                else
                    ptr = EMIT_SetFlagsConditional(ptr, cc, SR_Calt | SR_X, ARM_CC_CC);
            }
        }
    }

    return ptr;
}

uint32_t *EMIT_Scc(uint32_t *ptr, uint16_t opcode, uint16_t **m68k_ptr)
{
    uint8_t m68k_condition = (opcode >> 8) & 0x0f;
    uint8_t arm_condition = 0;
    uint8_t ext_count = 0;

    if ((opcode & 0x38) == 0)
    {
        /* Scc Dx case */
        uint8_t dest = RA_MapM68kRegister(&ptr, opcode & 7);
        RA_SetDirtyM68kRegister(&ptr, opcode & 7);

        /* T condition always sets lowest 8 bis, F condition always clears them */
        if ((opcode & 0x0f00) == 0x0100)
        {
            *ptr++ = bic_immed(dest, dest, 8, 0);
        }
        else if ((opcode & 0x0f00) == 0x0000)
        {
            *ptr++ = orr_immed(dest, dest, 8, 0);
        }
        else
        {
            arm_condition = EMIT_TestCondition(&ptr, m68k_condition);

            uint8_t tmp = RA_AllocARMRegister(&ptr);

            *ptr++ = csetm(tmp, arm_condition);
            *ptr++ = bfi(dest, tmp, 0, 8);

            RA_FreeARMRegister(&ptr, tmp);
        }
    }
    else
    {
        uint8_t dest = RA_AllocARMRegister(&ptr);
        uint8_t tmp = RA_AllocARMRegister(&ptr);

        /* T condition always sets lowest 8 bis, F condition always clears them */
        if ((opcode & 0x0f00) == 0x0100)
        {
            *ptr++ = mov_immed_u16(dest, 0, 0);
        }
        else if ((opcode & 0x0f00) == 0x0000)
        {
            *ptr++ = mov_immed_u16(dest, 0xff, 0);
        }
        else
        {
            arm_condition = EMIT_TestCondition(&ptr, m68k_condition);
            uint8_t tmp = RA_AllocARMRegister(&ptr);

            *ptr++ = csetm(tmp, arm_condition);
            *ptr++ = bfi(dest, tmp, 0, 8);

            RA_FreeARMRegister(&ptr, tmp);
        }

        ptr = EMIT_StoreToEffectiveAddress(ptr, 1, &dest, opcode & 0x3f, *m68k_ptr, &ext_count, 0);

        RA_FreeARMRegister(&ptr, tmp);
        RA_FreeARMRegister(&ptr, dest);
    }

    (*m68k_ptr) += ext_count;
    ptr = EMIT_AdvancePC(ptr, 2 * (ext_count + 1));

    return ptr;
}

uint32_t *EMIT_TRAPcc(uint32_t *ptr, uint16_t opcode, uint16_t **m68k_ptr)
{
    uint32_t source = (uint32_t)(uintptr_t)(*m68k_ptr - 1);
    uint8_t arm_condition = 0xff;
    uint8_t m68k_condition = (opcode >> 8) & 15;
    switch (opcode & 7)
    {
        case 4:
            ptr = EMIT_AdvancePC(ptr, 2);
            break;
        case 2:
            ptr = EMIT_AdvancePC(ptr, 4);
            (*m68k_ptr)++;
            break;
        case 3:
            ptr = EMIT_AdvancePC(ptr, 6);
            (*m68k_ptr)+=2;
            break;
        default:
            ptr = EMIT_InjectDebugString(ptr, "[JIT] Illegal OPMODE %d in TRAPcc at %08x. Opcode %04x\n", opcode & 7, source, opcode);
            ptr = EMIT_FlushPC(ptr);
            ptr = EMIT_Exception(ptr, VECTOR_ILLEGAL_INSTRUCTION, 0);
            *ptr++ = INSN_TO_LE(0xffffffff);
            break;
    }
    ptr = EMIT_FlushPC(ptr);

    /* If condition is TRUE, always generate exception */
    if (m68k_condition == M_CC_T)
    {
        ptr = EMIT_Exception(ptr, VECTOR_TRAPcc, 2, source);
        *ptr++ = INSN_TO_LE(0xffffffff);
    }
    /* If condition is FALSE, never generate exception, otherwise test CC */
    else if (m68k_condition != M_CC_F)
    {
        uint32_t *tmpptr;
        arm_condition = EMIT_TestCondition(&ptr, m68k_condition);

        tmpptr = ptr;
        *ptr++ = b_cc(arm_condition ^ 1, 0);
        ptr = EMIT_Exception(ptr, VECTOR_TRAPcc, 2, source);
        *tmpptr = b_cc(arm_condition ^ 1, ptr - tmpptr);
        *ptr++ = (uint32_t)(uintptr_t)tmpptr;
        *ptr++ = 1;
        *ptr++ = 0;
        *ptr++ = INSN_TO_LE(0xfffffffe);
    }
    
    return ptr;
}

uint32_t *EMIT_DBcc(uint32_t *ptr, uint16_t opcode, uint16_t **m68k_ptr)
{
    extern struct M68KState *__m68k_state;
    uint8_t counter_reg = RA_MapM68kRegister(&ptr, opcode & 7);
    uint8_t m68k_condition = (opcode >> 8) & 0x0f;
    uint8_t arm_condition = 0;
    uint32_t *branch_1 = NULL;
    uint32_t *branch_2 = NULL;
    int32_t branch_offset = 2 + (int16_t)cache_read_16(ICACHE, (uintptr_t)&(*(*m68k_ptr)++));
    uint16_t *bra_rel_ptr = *m68k_ptr - 2;

    /* Selcom case of DBT which does nothing */
    if (m68k_condition == M_CC_T)
    {
        /* Emu68 needs to emit at least one aarch64 opcode, push nop */
        *ptr++ = nop();
        ptr = EMIT_AdvancePC(ptr, 4);
    }
    else
    {
        uint8_t c_true = RA_AllocARMRegister(&ptr);
        uint8_t c_false = RA_AllocARMRegister(&ptr);
        int8_t off8 = 0;
        int32_t off = 4;

        // Suggested by Paraj - a way to allow old code using DBF as busy loop work:
        // For busy loops (of the form l dbf dN,l) in chip mem add extra delay that is
        // at least 10 7MHz clocks (For old school replayer routines)
        if (__m68k_state->JIT_CONTROL2 & JC2F_DBF_SLOWDOWN)
        {
            if (m68k_condition == M_CC_F && branch_offset == 0 && (uintptr_t)*m68k_ptr < 0x200000)
            {
                *ptr++ = mov_immed_u16(c_true, 0, 0);
                *ptr++ = ldrb_offset(c_true, c_false, 0);
                *ptr++ = ldrb_offset(c_true, c_false, 0);
                *ptr++ = ldrb_offset(c_true, c_false, 0);
            }
        }

        ptr = EMIT_GetOffsetPC(ptr, &off8);
        off += off8;
        ptr = EMIT_ResetOffsetPC(ptr);

        *ptr++ = add_immed(c_true, REG_PC, off);

        off = branch_offset + off8;

        if (off > -4096 && off < 0)
        {
            *ptr++ = sub_immed(c_false, REG_PC, -off);
        }
        else if (off > 0 && off < 4096)
        {
            *ptr++ = add_immed(c_false, REG_PC, off);
        }
        else if (off != 0)
        {
            uint8_t reg = RA_AllocARMRegister(&ptr);
            *ptr++ = movw_immed_u16(reg, off & 0xffff);
            *ptr++ = movt_immed_u16(reg, (off >> 16) & 0xffff);
            *ptr++ = add_reg(c_false, REG_PC, reg, LSL, 0);
            RA_FreeARMRegister(&ptr, reg);
        } else /* branch_offset == 0 */
        {
            *ptr++ = mov_reg(c_false, REG_PC);
        }

        /* If condition was not false check the condition and eventually break the loop */
        if (m68k_condition != M_CC_F)
        {
            arm_condition = EMIT_TestCondition(&ptr, m68k_condition);

            /* Adjust PC, negated CC is loop condition, CC is loop break condition */
            *ptr++ = csel(REG_PC, c_true, c_false, arm_condition);

            /* conditionally exit loop */
            branch_1 = ptr;
            *ptr++ = b_cc(arm_condition, 0);
        }

        /* Copy register to temporary, shift 16 bits left */
        uint8_t reg = RA_AllocARMRegister(&ptr);

        *ptr++ = uxth(reg, counter_reg);

        /* Substract 0x10000 from temporary, compare with 0xffff0000 */
        *ptr++ = subs_immed(reg, reg, 1);
        *ptr++ = bfi(counter_reg, reg, 0, 16);

        RA_SetDirtyM68kRegister(&ptr, opcode & 7);

        /* If counter was 0xffff (temprary reg 0xffff0000) break the loop */
        *ptr++ = csel(REG_PC, c_true, c_false, A64_CC_MI);
        branch_2 = ptr;
        *ptr++ = b_cc(A64_CC_PL, 3);
        
        if (branch_1) {
            *branch_1 = b_cc(arm_condition, ptr - branch_1);
        }

        RA_FreeARMRegister(&ptr, reg);

        *branch_2 = b_cc(A64_CC_PL, ptr - branch_2);
        
        *m68k_ptr = (void *)((uintptr_t)bra_rel_ptr + branch_offset);

        *ptr++ = (uint32_t)(uintptr_t)branch_2;
        *ptr++ = 1;
        *ptr++ = 0;
        *ptr++ = INSN_TO_LE(0xfffffffe);
        *ptr++ = INSN_TO_LE(0xfffffff1);

        RA_FreeARMRegister(&ptr, c_true);
        RA_FreeARMRegister(&ptr, c_false);
        RA_FreeARMRegister(&ptr, counter_reg);
    }

    return ptr;
}

static struct OpcodeDef InsnTable[512] = {
	[0000 ... 0007] = { { .od_Emit = EMIT_ADDQ }, NULL, 0, SR_CCR, 1, 0, 1 },
	[0020 ... 0047] = { { .od_Emit = EMIT_ADDQ }, NULL, 0, SR_CCR, 1, 0, 1 },
	[0050 ... 0071] = { { .od_Emit = EMIT_ADDQ }, NULL, 0, SR_CCR, 1, 1, 1 }, 
	[0100 ... 0107] = { { .od_Emit = EMIT_ADDQ }, NULL, 0, SR_CCR, 1, 0, 2 },
    [0110 ... 0117] = { { .od_Emit = EMIT_ADDQ }, NULL, 0, 0, 1, 0, 2 },
    [0120 ... 0147] = { { .od_Emit = EMIT_ADDQ }, NULL, 0, SR_CCR, 1, 0, 2 },
	[0150 ... 0171] = { { .od_Emit = EMIT_ADDQ }, NULL, 0, SR_CCR, 1, 1, 2 },
	[0200 ... 0207] = { { .od_Emit = EMIT_ADDQ }, NULL, 0, SR_CCR, 1, 0, 4 },
    [0210 ... 0217] = { { .od_Emit = EMIT_ADDQ }, NULL, 0, 0, 1, 0, 4 },
    [0220 ... 0247] = { { .od_Emit = EMIT_ADDQ }, NULL, 0, SR_CCR, 1, 0, 4 },
	[0250 ... 0271] = { { .od_Emit = EMIT_ADDQ }, NULL, 0, SR_CCR, 1, 1, 4 },

	[0300 ... 0307] = { { .od_Emit = EMIT_Scc }, NULL, SR_NZVC, 0, 1, 0, 1 },
	[0710 ... 0717] = { { .od_Emit = EMIT_DBcc }, NULL, SR_NZVC, 0, 2, 0, 0 },
	[0320 ... 0347] = { { .od_Emit = EMIT_Scc }, NULL, SR_NZVC, 0, 1, 0, 1 },
	[0350 ... 0371] = { { .od_Emit = EMIT_Scc }, NULL, SR_NZVC, 0, 1, 1, 1 },
	[0372]          = { { .od_Emit = EMIT_TRAPcc }, NULL, SR_CCR, 0, 2, 0, 0 },
    [0373]          = { { .od_Emit = EMIT_TRAPcc }, NULL, SR_CCR, 0, 3, 0, 0 },
    [0374]          = { { .od_Emit = EMIT_TRAPcc }, NULL, SR_CCR, 0, 1, 0, 0 },

	[0400 ... 0407] = { { .od_Emit = EMIT_SUBQ }, NULL, 0, SR_CCR, 1, 0, 1 },
	[0420 ... 0447] = { { .od_Emit = EMIT_SUBQ }, NULL, 0, SR_CCR, 1, 0, 1 },
	[0450 ... 0471] = { { .od_Emit = EMIT_SUBQ }, NULL, 0, SR_CCR, 1, 1, 1 },
	[0500 ... 0507] = { { .od_Emit = EMIT_SUBQ }, NULL, 0, SR_CCR, 1, 0, 2 },
    [0510 ... 0517] = { { .od_Emit = EMIT_SUBQ }, NULL, 0, 0, 1, 0, 2 },
    [0520 ... 0547] = { { .od_Emit = EMIT_SUBQ }, NULL, 0, SR_CCR, 1, 0, 2 },
	[0550 ... 0571] = { { .od_Emit = EMIT_SUBQ }, NULL, 0, SR_CCR, 1, 1, 2 },
	[0600 ... 0607] = { { .od_Emit = EMIT_SUBQ }, NULL, 0, SR_CCR, 1, 0, 4 },
    [0610 ... 0617] = { { .od_Emit = EMIT_SUBQ }, NULL, 0, 0, 1, 0, 4 },
    [0620 ... 0647] = { { .od_Emit = EMIT_SUBQ }, NULL, 0, SR_CCR, 1, 0, 4 },
	[0650 ... 0671] = { { .od_Emit = EMIT_SUBQ }, NULL, 0, SR_CCR, 1, 1, 4 },

	[0700 ... 0707] = { { .od_Emit = EMIT_Scc }, NULL, SR_NZVC, 0, 1, 0, 1  },
	[0310 ... 0317] = { { .od_Emit = EMIT_DBcc }, NULL, SR_NZVC, 0, 2, 0, 0 },
	[0720 ... 0747] = { { .od_Emit = EMIT_Scc }, NULL, SR_NZVC, 0, 1, 0, 1  },
	[0750 ... 0771] = { { .od_Emit = EMIT_Scc }, NULL, SR_NZVC, 0, 1, 1, 1  },
	[0772]          = { { .od_Emit = EMIT_TRAPcc }, NULL, SR_CCR, 0, 2, 0, 0},
    [0773]          = { { .od_Emit = EMIT_TRAPcc }, NULL, SR_CCR, 0, 3, 0, 0},
    [0774]          = { { .od_Emit = EMIT_TRAPcc }, NULL, SR_CCR, 0, 1, 0, 0},
};

uint32_t *EMIT_line5(uint32_t *ptr, uint16_t **m68k_ptr, uint16_t *insn_consumed)
{
    uint16_t opcode = cache_read_16(ICACHE, (uintptr_t)&(*m68k_ptr)[0]);
    (*m68k_ptr)++;
    *insn_consumed = 1;

    if (InsnTable[opcode & 0777].od_Emit) {
        ptr = InsnTable[opcode & 0777].od_Emit(ptr, opcode, m68k_ptr);
    }
    else
    {
        ptr = EMIT_FlushPC(ptr);
        ptr = EMIT_InjectDebugString(ptr, "[JIT] opcode %04x at %08x not implemented\n", opcode, *m68k_ptr - 1);
        *ptr++ = svc(0x100);
        *ptr++ = svc(0x101);
        *ptr++ = svc(0x103);
        *ptr++ = (uint32_t)(uintptr_t)(*m68k_ptr - 8);
        *ptr++ = 48;
        ptr = EMIT_Exception(ptr, VECTOR_ILLEGAL_INSTRUCTION, 0);
        *ptr++ = INSN_TO_LE(0xffffffff);
    }

    return ptr;
}


uint32_t GetSR_Line5(uint16_t opcode)
{
    /* If instruction is in the table, return what flags it needs (shifted 16 bits left) and flags it sets */
    if (InsnTable[opcode & 0777].od_Emit) {
        return (InsnTable[opcode & 0777].od_SRNeeds << 16) | InsnTable[opcode & 0777].od_SRSets;
    }
    /* Instruction not found, i.e. it needs all flags and sets none (ILLEGAL INSTRUCTION exception) */
    else {
        kprintf("Undefined Line5\n");
        return SR_CCR << 16;
    }
}

int M68K_GetLine5Length(uint16_t *insn_stream)
{
    uint16_t opcode = cache_read_16(ICACHE, (uintptr_t)&(*insn_stream));
    
    int length = 0;
    int need_ea = 0;
    int opsize = 0;

    if (InsnTable[opcode & 0777].od_Emit) {
        length = InsnTable[opcode & 0777].od_BaseLength;
        need_ea = InsnTable[opcode & 0777].od_HasEA;
        opsize = InsnTable[opcode & 0777].od_OpSize;
    }

    if (need_ea) {
        length += SR_GetEALength(&insn_stream[length], opcode & 0x3f, opsize);
    }

    return length;
}
