/*
 * Copyright (C) 2009-2011 STMicroelectronics. All rights reserved.
 * Copyright (C) 2008 Apple Inc. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE INC. OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#ifndef SH4Assembler_h
#define SH4Assembler_h

#if ENABLE(ASSEMBLER) && CPU(SH4)

#include "AssemblerBuffer.h"
#include "AssemblerBufferWithConstantPool.h"
#include <stdarg.h>
#include <stdint.h>
#include <wtf/Assertions.h>
#include <wtf/Vector.h>

#ifndef NDEBUG
#define SH4_ASSEMBLER_TRACING
#endif

namespace JSC {
typedef uint16_t SH4Word;

enum {
    INVALID_OPCODE = 0xffff,
    ADD_OPCODE = 0x300c,
    ADDIMM_OPCODE = 0x7000,
    ADDC_OPCODE = 0x300e,
    ADDV_OPCODE = 0x300f,
    AND_OPCODE = 0x2009,
    ANDIMM_OPCODE = 0xc900,
    DIV0_OPCODE = 0x2007,
    DIV1_OPCODE = 0x3004,
    BF_OPCODE = 0x8b00,
    BFS_OPCODE = 0x8f00,
    BRA_OPCODE = 0xa000,
    BRAF_OPCODE = 0x0023,
    NOP_OPCODE = 0x0009,
    BSR_OPCODE = 0xb000,
    RTS_OPCODE = 0x000b,
    BT_OPCODE = 0x8900,
    BTS_OPCODE = 0x8d00,
    BSRF_OPCODE = 0x0003,
    BRK_OPCODE = 0x003b,
    FTRC_OPCODE = 0xf03d,
    CMPEQ_OPCODE = 0x3000,
    CMPEQIMM_OPCODE = 0x8800,
    CMPGE_OPCODE = 0x3003,
    CMPGT_OPCODE = 0x3007,
    CMPHI_OPCODE = 0x3006,
    CMPHS_OPCODE = 0x3002,
    CMPPL_OPCODE = 0x4015,
    CMPPZ_OPCODE = 0x4011,
    CMPSTR_OPCODE = 0x200c,
    DT_OPCODE = 0x4010,
    FCMPEQ_OPCODE = 0xf004,
    FCMPGT_OPCODE = 0xf005,
    FMOV_OPCODE = 0xf00c,
    FADD_OPCODE = 0xf000,
    FMUL_OPCODE = 0xf002,
    FSUB_OPCODE = 0xf001,
    FDIV_OPCODE = 0xf003,
    FNEG_OPCODE = 0xf04d,
    JMP_OPCODE = 0x402b,
    JSR_OPCODE = 0x400b,
    LDSPR_OPCODE = 0x402a,
    LDSLPR_OPCODE = 0x4026,
    MOV_OPCODE = 0x6003,
    MOVIMM_OPCODE = 0xe000,
    MOVB_WRITE_RN_OPCODE = 0x2000,
    MOVB_WRITE_RNDEC_OPCODE = 0x2004,
    MOVB_WRITE_R0RN_OPCODE = 0x0004,
    MOVB_WRITE_OFFGBR_OPCODE = 0xc000,
    MOVB_WRITE_OFFRN_OPCODE = 0x8000,
    MOVB_READ_RM_OPCODE = 0x6000,
    MOVB_READ_RMINC_OPCODE = 0x6004,
    MOVB_READ_R0RM_OPCODE = 0x000c,
    MOVB_READ_OFFGBR_OPCODE = 0xc400,
    MOVB_READ_OFFRM_OPCODE = 0x8400,
    MOVL_WRITE_RN_OPCODE = 0x2002,
    MOVL_WRITE_RNDEC_OPCODE = 0x2006,
    MOVL_WRITE_R0RN_OPCODE = 0x0006,
    MOVL_WRITE_OFFGBR_OPCODE = 0xc200,
    MOVL_WRITE_OFFRN_OPCODE = 0x1000,
    MOVL_READ_RM_OPCODE = 0x6002,
    MOVL_READ_RMINC_OPCODE = 0x6006,
    MOVL_READ_R0RM_OPCODE = 0x000e,
    MOVL_READ_OFFGBR_OPCODE = 0xc600,
    MOVL_READ_OFFPC_OPCODE = 0xd000,
    MOVL_READ_OFFRM_OPCODE = 0x5000,
    MOVW_WRITE_RN_OPCODE = 0x2001,
    MOVW_READ_RM_OPCODE = 0x6001,
    MOVW_READ_R0RM_OPCODE = 0x000d,
    MOVW_READ_OFFRM_OPCODE = 0x8500,
    MOVW_READ_OFFPC_OPCODE = 0x9000,
    MOVA_READ_OFFPC_OPCODE = 0xc700,
    MOVT_OPCODE = 0x0029,
    MULL_OPCODE = 0x0007,
    DMULL_L_OPCODE = 0x3005,
    STSMACL_OPCODE = 0x001a,
    STSMACH_OPCODE = 0x000a,
    DMULSL_OPCODE = 0x300d,
    NEG_OPCODE = 0x600b,
    NEGC_OPCODE = 0x600a,
    NOT_OPCODE = 0x6007,
    OR_OPCODE = 0x200b,
    ORIMM_OPCODE = 0xcb00,
    ORBIMM_OPCODE = 0xcf00,
    SETS_OPCODE = 0x0058,
    SETT_OPCODE = 0x0018,
    SHAD_OPCODE = 0x400c,
    SHAL_OPCODE = 0x4020,
    SHAR_OPCODE = 0x4021,
    SHLD_OPCODE = 0x400d,
    SHLL_OPCODE = 0x4000,
    SHLL2_OPCODE = 0x4008,
    SHLL8_OPCODE = 0x4018,
    SHLL16_OPCODE = 0x4028,
    SHLR_OPCODE = 0x4001,
    SHLR2_OPCODE = 0x4009,
    SHLR8_OPCODE = 0x4019,
    SHLR16_OPCODE = 0x4029,
    STSPR_OPCODE = 0x002a,
    STSLPR_OPCODE = 0x4022,
    FLOAT_OPCODE = 0xf02d,
    SUB_OPCODE = 0x3008,
    SUBC_OPCODE = 0x300a,
    SUBV_OPCODE = 0x300b,
    TST_OPCODE = 0x2008,
    TSTIMM_OPCODE = 0xc800,
    TSTB_OPCODE = 0xcc00,
    EXTUW_OPCODE = 0x600d,
    XOR_OPCODE = 0x200a,
    XORIMM_OPCODE = 0xca00,
    XORB_OPCODE = 0xce00,
    FMOVS_READ_RM_INC_OPCODE = 0xf009,
    FMOVS_READ_RM_OPCODE = 0xf008,
    FMOVS_READ_R0RM_OPCODE = 0xf006,
    FMOVS_WRITE_RN_OPCODE = 0xf00a,
    FMOVS_WRITE_RN_DEC_OPCODE = 0xf00b,
    FMOVS_WRITE_R0RN_OPCODE = 0xf007,
    FCNVDS_DRM_FPUL_OPCODE = 0xf0bd,
    LDS_RM_FPUL_OPCODE = 0x405a,
    FLDS_FRM_FPUL_OPCODE = 0xf01d,
    STS_FPUL_RN_OPCODE = 0x005a,
    FSTS_FPUL_FRN_OPCODE = 0xF00d,
    LDSFPSCR_OPCODE = 0x406a,
    STSFPSCR_OPCODE = 0x006a,
    LDSRMFPUL_OPCODE = 0x405a,
    FSTSFPULFRN_OPCODE = 0xf00d,
    FSQRT_OPCODE = 0xf06d,
    FSCHG_OPCODE = 0xf3fd,
    CLRT_OPCODE = 8,
};

namespace SH4Registers {
typedef enum {
    r0,
    r1,
    r2,
    r3,
    r4,
    r5,
    r6,
    r7,
    r8,
    r9,
    r10,
    r11,
    r12,
    r13,
    r14, fp = r14,
    r15, sp = r15,
    pc,
    pr,
} RegisterID;

typedef enum {
    fr0, dr0 = fr0,
    fr1,
    fr2, dr2 = fr2,
    fr3,
    fr4, dr4 = fr4,
    fr5,
    fr6, dr6 = fr6,
    fr7,
    fr8, dr8 = fr8,
    fr9,
    fr10, dr10 = fr10,
    fr11,
    fr12, dr12 = fr12,
    fr13,
    fr14, dr14 = fr14,
    fr15,
} FPRegisterID;
}

inline uint16_t getOpcodeGroup1(uint16_t opc, int rm, int rn)
{
    return (opc | ((rm & 0xf) << 8) | ((rn & 0xf) << 4));
}

inline uint16_t getOpcodeGroup2(uint16_t opc, int rm)
{
    return (opc | ((rm & 0xf) << 8));
}

inline uint16_t getOpcodeGroup3(uint16_t opc, int rm, int rn)
{
    return (opc | ((rm & 0xf) << 8) | (rn & 0xff));
}

inline uint16_t getOpcodeGroup4(uint16_t opc, int rm, int rn, int offset)
{
    return (opc | ((rm & 0xf) << 8) | ((rn & 0xf) << 4) | (offset & 0xf));
}

inline uint16_t getOpcodeGroup5(uint16_t opc, int rm)
{
    return (opc | (rm & 0xff));
}

inline uint16_t getOpcodeGroup6(uint16_t opc, int rm)
{
    return (opc | (rm & 0xfff));
}

inline uint16_t getOpcodeGroup7(uint16_t opc, int rm)
{
    return (opc | ((rm & 0x7) << 9));
}

inline uint16_t getOpcodeGroup8(uint16_t opc, int rm, int rn)
{
    return (opc | ((rm & 0x7) << 9) | ((rn & 0x7) << 5));
}

inline uint16_t getOpcodeGroup9(uint16_t opc, int rm, int rn)
{
    return (opc | ((rm & 0xf) << 8) | ((rn & 0x7) << 5));
}

inline uint16_t getOpcodeGroup10(uint16_t opc, int rm, int rn)
{
    return (opc | ((rm & 0x7) << 9) | ((rn & 0xf) << 4));
}

inline uint16_t getOpcodeGroup11(uint16_t opc, int rm, int rn)
{
    return (opc | ((rm & 0xf) << 4) | (rn & 0xf));
}

inline uint16_t getRn(uint16_t x)
{
    return ((x & 0xf00) >> 8);
}

inline uint16_t getRm(uint16_t x)
{
    return ((x & 0xf0) >> 4);
}

inline uint16_t getDisp(uint16_t x)
{
    return (x & 0xf);
}

inline uint16_t getImm8(uint16_t x)
{
    return (x & 0xff);
}

inline uint16_t getImm12(uint16_t x)
{
    return (x & 0xfff);
}

inline uint16_t getDRn(uint16_t x)
{
    return ((x & 0xe00) >> 9);
}

inline uint16_t getDRm(uint16_t x)
{
    return ((x & 0xe0) >> 5);
}

class SH4Assembler {
public:
    typedef SH4Registers::RegisterID RegisterID;
    typedef SH4Registers::FPRegisterID FPRegisterID;
    typedef AssemblerBufferWithConstantPool<512, 4, 2, SH4Assembler> SH4Buffer;
    static const RegisterID scratchReg1 = SH4Registers::r3;
    static const RegisterID scratchReg2 = SH4Registers::r11;
    static const uint32_t maxInstructionSize = 16;

    enum {
        padForAlign8 = 0x00,
        padForAlign16 = 0x0009,
        padForAlign32 = 0x00090009,
    };

    SH4Assembler()
    {
        m_claimscratchReg = 0x0;
    }

    // SH4 condition codes
    typedef enum {
        EQ = 0x0, // Equal
        NE = 0x1, // Not Equal
        HS = 0x2, // Unsigend Greater Than equal
        HI = 0x3, // Unsigend Greater Than
        LS = 0x4, // Unsigend Lower or Same
        LI = 0x5, // Unsigend Lower
        GE = 0x6, // Greater or Equal
        LT = 0x7, // Less Than
        GT = 0x8, // Greater Than
        LE = 0x9, // Less or Equal
        OF = 0xa, // OverFlow
        SI = 0xb, // Signed
        EQU= 0xc, // Equal or unordered(NaN)
        NEU= 0xd,
        GTU= 0xe,
        GEU= 0xf,
        LTU= 0x10,
        LEU= 0x11,
    } Condition;

    // Opaque label types
public:
    bool isImmediate(int constant)
    {
        return ((constant <= 127) && (constant >= -128));
    }

    RegisterID claimScratch()
    {
        ASSERT((m_claimscratchReg != 0x3));

        if (!(m_claimscratchReg & 0x1)) {
            m_claimscratchReg = (m_claimscratchReg | 0x1);
            return scratchReg1;
        }

        m_claimscratchReg = (m_claimscratchReg | 0x2);
        return scratchReg2;
    }

    void releaseScratch(RegisterID scratchR)
    {
        if (scratchR == scratchReg1)
            m_claimscratchReg = (m_claimscratchReg & 0x2);
        else
            m_claimscratchReg = (m_claimscratchReg & 0x1);
    }

    // Stack operations

    void pushReg(RegisterID reg)
    {
        if (reg == SH4Registers::pr) {
            oneShortOp(getOpcodeGroup2(STSLPR_OPCODE, SH4Registers::sp));
            return;
        }

        oneShortOp(getOpcodeGroup1(MOVL_WRITE_RNDEC_OPCODE, SH4Registers::sp, reg));
    }

    void popReg(RegisterID reg)
    {
        if (reg == SH4Registers::pr) {
            oneShortOp(getOpcodeGroup2(LDSLPR_OPCODE, SH4Registers::sp));
            return;
        }

        oneShortOp(getOpcodeGroup1(MOVL_READ_RMINC_OPCODE, reg, SH4Registers::sp));
    }

    void movt(RegisterID dst)
    {
        uint16_t opc = getOpcodeGroup2(MOVT_OPCODE, dst);
        oneShortOp(opc);
    }

    // Arithmetic operations

    void addlRegReg(RegisterID src, RegisterID dst)
    {
        uint16_t opc = getOpcodeGroup1(ADD_OPCODE, dst, src);
        oneShortOp(opc);
    }

    void addclRegReg(RegisterID src, RegisterID dst)
    {
        uint16_t opc = getOpcodeGroup1(ADDC_OPCODE, dst, src);
        oneShortOp(opc);
    }

    void addvlRegReg(RegisterID src, RegisterID dst)
    {
        uint16_t opc = getOpcodeGroup1(ADDV_OPCODE, dst, src);
        oneShortOp(opc);
    }

    void addlImm8r(int imm8, RegisterID dst)
    {
        ASSERT((imm8 <= 127) && (imm8 >= -128));

        uint16_t opc = getOpcodeGroup3(ADDIMM_OPCODE, dst, imm8);
        oneShortOp(opc);
    }

    void andlRegReg(RegisterID src, RegisterID dst)
    {
        uint16_t opc = getOpcodeGroup1(AND_OPCODE, dst, src);
        oneShortOp(opc);
    }

    void andlImm8r(int imm8, RegisterID dst)
    {
        ASSERT((imm8 <= 255) && (imm8 >= 0));
        ASSERT(dst == SH4Registers::r0);

        uint16_t opc = getOpcodeGroup5(ANDIMM_OPCODE, imm8);
        oneShortOp(opc);
    }

    void div1lRegReg(RegisterID src, RegisterID dst)
    {
        uint16_t opc = getOpcodeGroup1(DIV1_OPCODE, dst, src);
        oneShortOp(opc);
    }

    void div0lRegReg(RegisterID src, RegisterID dst)
    {
        uint16_t opc = getOpcodeGroup1(DIV0_OPCODE, dst, src);
        oneShortOp(opc);
    }

    void notlReg(RegisterID src, RegisterID dst)
    {
        uint16_t opc = getOpcodeGroup1(NOT_OPCODE, dst, src);
        oneShortOp(opc);
    }

    void orlRegReg(RegisterID src, RegisterID dst)
    {
        uint16_t opc = getOpcodeGroup1(OR_OPCODE, dst, src);
        oneShortOp(opc);
    }

    void orlImm8r(int imm8, RegisterID dst)
    {
        ASSERT((imm8 <= 255) && (imm8 >= 0));
        ASSERT(dst == SH4Registers::r0);

        uint16_t opc = getOpcodeGroup5(ORIMM_OPCODE, imm8);
        oneShortOp(opc);
    }

    void sublRegReg(RegisterID src, RegisterID dst)
    {
         uint16_t opc = getOpcodeGroup1(SUB_OPCODE, dst, src);
         oneShortOp(opc);
    }

    void subvlRegReg(RegisterID src, RegisterID dst)
    {
         uint16_t opc = getOpcodeGroup1(SUBV_OPCODE, dst, src);
         oneShortOp(opc);
    }

    void xorlRegReg(RegisterID src, RegisterID dst)
    {
        uint16_t opc = getOpcodeGroup1(XOR_OPCODE, dst, src);
        oneShortOp(opc);
    }

    void xorlImm8r(int imm8, RegisterID dst)
    {
        ASSERT((imm8 <= 255) && (imm8 >= 0));
        ASSERT(dst == SH4Registers::r0);

        uint16_t opc = getOpcodeGroup5(XORIMM_OPCODE, imm8);
        oneShortOp(opc);
    }

    void shllImm8r(int imm, RegisterID dst)
    {
        switch (imm) {
        case 1:
            oneShortOp(getOpcodeGroup2(SHLL_OPCODE, dst));
            break;
        case 2:
            oneShortOp(getOpcodeGroup2(SHLL2_OPCODE, dst));
            break;
        case 8:
            oneShortOp(getOpcodeGroup2(SHLL8_OPCODE, dst));
            break;
        case 16:
            oneShortOp(getOpcodeGroup2(SHLL16_OPCODE, dst));
            break;
        default:
            ASSERT_NOT_REACHED();
        }
    }

    void neg(RegisterID dst, RegisterID src)
    {
        uint16_t opc = getOpcodeGroup1(NEG_OPCODE, dst, src);
        oneShortOp(opc);
    }

    void shllRegReg(RegisterID dst, RegisterID rShift)
    {
        uint16_t opc = getOpcodeGroup1(SHLD_OPCODE, dst, rShift);
        oneShortOp(opc);
    }

    void shlrRegReg(RegisterID dst, RegisterID rShift)
    {
        neg(rShift, rShift);
        shllRegReg(dst, rShift);
    }

    void sharRegReg(RegisterID dst, RegisterID rShift)
    {
        neg(rShift, rShift);
        shaRegReg(dst, rShift);
    }

    void shaRegReg(RegisterID dst, RegisterID rShift)
    {
        uint16_t opc = getOpcodeGroup1(SHAD_OPCODE, dst, rShift);
        oneShortOp(opc);
    }

    void shlrImm8r(int imm, RegisterID dst)
    {
        switch (imm) {
        case 1:
            oneShortOp(getOpcodeGroup2(SHLR_OPCODE, dst));
            break;
        case 2:
            oneShortOp(getOpcodeGroup2(SHLR2_OPCODE, dst));
            break;
        case 8:
            oneShortOp(getOpcodeGroup2(SHLR8_OPCODE, dst));
            break;
        case 16:
            oneShortOp(getOpcodeGroup2(SHLR16_OPCODE, dst));
            break;
        default:
            ASSERT_NOT_REACHED();
        }
    }

    void imullRegReg(RegisterID src, RegisterID dst)
    {
        uint16_t opc = getOpcodeGroup1(MULL_OPCODE, dst, src);
        oneShortOp(opc);
    }

    void dmullRegReg(RegisterID src, RegisterID dst)
    {
        uint16_t opc = getOpcodeGroup1(DMULL_L_OPCODE, dst, src);
        oneShortOp(opc);
    }

    void dmulslRegReg(RegisterID src, RegisterID dst)
    {
        uint16_t opc = getOpcodeGroup1(DMULSL_OPCODE, dst, src);
        oneShortOp(opc);
    }

    void stsmacl(RegisterID reg)
    {
        uint16_t opc = getOpcodeGroup2(STSMACL_OPCODE, reg);
        oneShortOp(opc);
    }

    void stsmach(RegisterID reg)
    {
        uint16_t opc = getOpcodeGroup2(STSMACH_OPCODE, reg);
        oneShortOp(opc);
    }

    // Comparisons

    void cmplRegReg(RegisterID left, RegisterID right, Condition cond)
    {
        switch (cond) {
        case NE:
            oneShortOp(getOpcodeGroup1(CMPEQ_OPCODE, right, left));
            break;
        case GT:
            oneShortOp(getOpcodeGroup1(CMPGT_OPCODE, right, left));
            break;
        case EQ:
            oneShortOp(getOpcodeGroup1(CMPEQ_OPCODE, right, left));
            break;
        case GE:
            oneShortOp(getOpcodeGroup1(CMPGE_OPCODE, right, left));
            break;
        case HS:
            oneShortOp(getOpcodeGroup1(CMPHS_OPCODE, right, left));
            break;
        case HI:
            oneShortOp(getOpcodeGroup1(CMPHI_OPCODE, right, left));
            break;
        case LI:
            oneShortOp(getOpcodeGroup1(CMPHI_OPCODE, left, right));
            break;
        case LS:
            oneShortOp(getOpcodeGroup1(CMPHS_OPCODE, left, right));
            break;
        case LE:
            oneShortOp(getOpcodeGroup1(CMPGE_OPCODE, left, right));
            break;
        case LT:
            oneShortOp(getOpcodeGroup1(CMPGT_OPCODE, left, right));
            break;
        default:
            ASSERT_NOT_REACHED();
        }
    }

    void cmppl(RegisterID reg)
    {
        uint16_t opc = getOpcodeGroup2(CMPPL_OPCODE, reg);
        oneShortOp(opc);
    }

    void cmppz(RegisterID reg)
    {
        uint16_t opc = getOpcodeGroup2(CMPPZ_OPCODE, reg);
        oneShortOp(opc);
    }

    void cmpEqImmR0(int imm, RegisterID dst)
    {
        uint16_t opc = getOpcodeGroup5(CMPEQIMM_OPCODE, imm);
        oneShortOp(opc);
    }

    void testlRegReg(RegisterID src, RegisterID dst)
    {
        uint16_t opc = getOpcodeGroup1(TST_OPCODE, dst, src);
        oneShortOp(opc);
    }

    void testlImm8r(int imm, RegisterID dst)
    {
        ASSERT((dst == SH4Registers::r0) && (imm <= 255) && (imm >= 0));

        uint16_t opc = getOpcodeGroup5(TSTIMM_OPCODE, imm);
        oneShortOp(opc);
    }

    void nop()
    {
        oneShortOp(NOP_OPCODE, false);
    }

    void sett()
    {
        oneShortOp(SETT_OPCODE);
    }

    void clrt()
    {
        oneShortOp(CLRT_OPCODE);
    }

    void fschg()
    {
        oneShortOp(FSCHG_OPCODE);
    }

    void bkpt()
    {
        oneShortOp(BRK_OPCODE, false);
    }

    void branch(uint16_t opc, int label)
    {
        switch (opc) {
        case BT_OPCODE:
            ASSERT((label <= 127) && (label >= -128));
            oneShortOp(getOpcodeGroup5(BT_OPCODE, label));
            break;
        case BRA_OPCODE:
            ASSERT((label <= 2047) && (label >= -2048));
            oneShortOp(getOpcodeGroup6(BRA_OPCODE, label));
            break;
        case BF_OPCODE:
            ASSERT((label <= 127) && (label >= -128));
            oneShortOp(getOpcodeGroup5(BF_OPCODE, label));
            break;
        default:
            ASSERT_NOT_REACHED();
        }
    }

    void branch(uint16_t opc, RegisterID reg)
    {
        switch (opc) {
        case BRAF_OPCODE:
            oneShortOp(getOpcodeGroup2(BRAF_OPCODE, reg));
            break;
        case JMP_OPCODE:
            oneShortOp(getOpcodeGroup2(JMP_OPCODE, reg));
            break;
        case JSR_OPCODE:
            oneShortOp(getOpcodeGroup2(JSR_OPCODE, reg));
            break;
        case BSRF_OPCODE:
            oneShortOp(getOpcodeGroup2(BSRF_OPCODE, reg));
            break;
        default:
            ASSERT_NOT_REACHED();
        }
    }

    void ldspr(RegisterID reg)
    {
        uint16_t opc = getOpcodeGroup2(LDSPR_OPCODE, reg);
        oneShortOp(opc);
    }

    void stspr(RegisterID reg)
    {
        uint16_t opc = getOpcodeGroup2(STSPR_OPCODE, reg);
        oneShortOp(opc);
    }

    void extuw(RegisterID src, RegisterID dst)
    {
        uint16_t opc = getOpcodeGroup1(EXTUW_OPCODE, dst, src);
        oneShortOp(opc);
    }

    // float operations

    void ldsrmfpul(RegisterID src)
    {
        uint16_t opc = getOpcodeGroup2(LDS_RM_FPUL_OPCODE, src);
        oneShortOp(opc);
    }

    void fneg(FPRegisterID dst)
    {
        uint16_t opc = getOpcodeGroup2(FNEG_OPCODE, dst);
        oneShortOp(opc, true, false);
    }

    void fsqrt(FPRegisterID dst)
    {
        uint16_t opc = getOpcodeGroup2(FSQRT_OPCODE, dst);
        oneShortOp(opc, true, false);
    }

    void stsfpulReg(RegisterID src)
    {
        uint16_t opc = getOpcodeGroup2(STS_FPUL_RN_OPCODE, src);
        oneShortOp(opc);
    }

    void floatfpulfrn(RegisterID src)
    {
        uint16_t opc = getOpcodeGroup2(FLOAT_OPCODE, src);
        oneShortOp(opc, true, false);
    }

    void fmull(FPRegisterID src, FPRegisterID dst)
    {
        uint16_t opc = getOpcodeGroup1(FMUL_OPCODE, dst, src);
        oneShortOp(opc, true, false);
    }

    void fmovsReadrm(RegisterID src, FPRegisterID dst)
    {
        uint16_t opc = getOpcodeGroup1(FMOVS_READ_RM_OPCODE, dst, src);
        oneShortOp(opc, true, false);
    }

    void fmovsWriterm(FPRegisterID src, RegisterID dst)
    {
        uint16_t opc = getOpcodeGroup1(FMOVS_WRITE_RN_OPCODE, dst, src);
        oneShortOp(opc, true, false);
    }

    void fmovsWriter0r(FPRegisterID src, RegisterID dst)
    {
        uint16_t opc = getOpcodeGroup1(FMOVS_WRITE_R0RN_OPCODE, dst, src);
        oneShortOp(opc, true, false);
    }

    void fmovsReadr0r(RegisterID src, FPRegisterID dst)
    {
        uint16_t opc = getOpcodeGroup1(FMOVS_READ_R0RM_OPCODE, dst, src);
        oneShortOp(opc, true, false);
    }

    void fmovsReadrminc(RegisterID src, FPRegisterID dst)
    {
        uint16_t opc = getOpcodeGroup1(FMOVS_READ_RM_INC_OPCODE, dst, src);
        oneShortOp(opc, true, false);
    }

    void fmovsWriterndec(FPRegisterID src, RegisterID dst)
    {
        uint16_t opc = getOpcodeGroup1(FMOVS_WRITE_RN_DEC_OPCODE, dst, src);
        oneShortOp(opc, true, false);
    }

    void ftrcRegfpul(FPRegisterID src)
    {
        uint16_t opc = getOpcodeGroup2(FTRC_OPCODE, src);
        oneShortOp(opc, true, false);
    }

    void fldsfpul(RegisterID src)
    {
        uint16_t opc = getOpcodeGroup2(FLDS_FRM_FPUL_OPCODE, src);
        oneShortOp(opc);
    }

    void fstsfpul(RegisterID src)
    {
        uint16_t opc = getOpcodeGroup2(FSTS_FPUL_FRN_OPCODE, src);
        oneShortOp(opc);
    }

    void ldsfpscr(RegisterID reg)
    {
        uint16_t opc = getOpcodeGroup2(LDSFPSCR_OPCODE, reg);
        oneShortOp(opc);
    }

    void stsfpscr(RegisterID reg)
    {
        uint16_t opc = getOpcodeGroup2(STSFPSCR_OPCODE, reg);
        oneShortOp(opc);
    }

    // double operations

    void dcnvds(FPRegisterID src)
    {
        uint16_t opc = getOpcodeGroup7(FCNVDS_DRM_FPUL_OPCODE, src >> 1);
        oneShortOp(opc);
    }

    void dcmppeq(FPRegisterID src, FPRegisterID dst)
    {
        uint16_t opc = getOpcodeGroup8(FCMPEQ_OPCODE, dst >> 1, src >> 1);
        oneShortOp(opc);
    }

    void dcmppgt(FPRegisterID src, FPRegisterID dst)
    {
        uint16_t opc = getOpcodeGroup8(FCMPGT_OPCODE, dst >> 1, src >> 1);
        oneShortOp(opc);
    }

    void dmulRegReg(FPRegisterID src, FPRegisterID dst)
    {
        uint16_t opc = getOpcodeGroup8(FMUL_OPCODE, dst >> 1, src >> 1);
        oneShortOp(opc);
    }

    void dsubRegReg(FPRegisterID src, FPRegisterID dst)
    {
        uint16_t opc = getOpcodeGroup8(FSUB_OPCODE, dst >> 1, src >> 1);
        oneShortOp(opc);
    }

    void daddRegReg(FPRegisterID src, FPRegisterID dst)
    {
        uint16_t opc = getOpcodeGroup8(FADD_OPCODE, dst >> 1, src >> 1);
        oneShortOp(opc);
    }

    void dmovRegReg(FPRegisterID src, FPRegisterID dst)
    {
        uint16_t opc = getOpcodeGroup8(FMOV_OPCODE, dst >> 1, src >> 1);
        oneShortOp(opc);
    }

    void ddivRegReg(FPRegisterID src, FPRegisterID dst)
    {
        uint16_t opc = getOpcodeGroup8(FDIV_OPCODE, dst >> 1, src >> 1);
        oneShortOp(opc);
    }

    void dsqrt(FPRegisterID dst)
    {
        uint16_t opc = getOpcodeGroup7(FSQRT_OPCODE, dst >> 1);
        oneShortOp(opc);
    }

    void dneg(FPRegisterID dst)
    {
        uint16_t opc = getOpcodeGroup7(FNEG_OPCODE, dst >> 1);
        oneShortOp(opc);
    }

    void fmovReadrm(RegisterID src, FPRegisterID dst)
    {
        uint16_t opc = getOpcodeGroup10(FMOVS_READ_RM_OPCODE, dst >> 1, src);
        oneShortOp(opc);
    }

    void fmovWriterm(FPRegisterID src, RegisterID dst)
    {
        uint16_t opc = getOpcodeGroup9(FMOVS_WRITE_RN_OPCODE, dst, src >> 1);
        oneShortOp(opc);
    }

    void fmovWriter0r(FPRegisterID src, RegisterID dst)
    {
        uint16_t opc = getOpcodeGroup9(FMOVS_WRITE_R0RN_OPCODE, dst, src >> 1);
        oneShortOp(opc);
    }

    void fmovReadr0r(RegisterID src, FPRegisterID dst)
    {
        uint16_t opc = getOpcodeGroup10(FMOVS_READ_R0RM_OPCODE, dst >> 1, src);
        oneShortOp(opc);
    }

    void fmovReadrminc(RegisterID src, FPRegisterID dst)
    {
        uint16_t opc = getOpcodeGroup10(FMOVS_READ_RM_INC_OPCODE, dst >> 1, src);
        oneShortOp(opc);
    }

    void fmovWriterndec(FPRegisterID src, RegisterID dst)
    {
        uint16_t opc = getOpcodeGroup9(FMOVS_WRITE_RN_DEC_OPCODE, dst, src >> 1);
        oneShortOp(opc);
    }

    void floatfpulDreg(FPRegisterID src)
    {
        uint16_t opc = getOpcodeGroup7(FLOAT_OPCODE, src >> 1);
        oneShortOp(opc);
    }

    void ftrcdrmfpul(FPRegisterID src)
    {
        uint16_t opc = getOpcodeGroup7(FTRC_OPCODE, src >> 1);
        oneShortOp(opc);
    }

    // Various move ops

    void movImm8(int imm8, RegisterID dst)
    {
        ASSERT((imm8 <= 127) && (imm8 >= -128));

        uint16_t opc = getOpcodeGroup3(MOVIMM_OPCODE, dst, imm8);
        oneShortOp(opc);
    }

    void movlRegReg(RegisterID src, RegisterID dst)
    {
        uint16_t opc = getOpcodeGroup1(MOV_OPCODE, dst, src);
        oneShortOp(opc);
    }

    void movwRegMem(RegisterID src, RegisterID dst)
    {
        uint16_t opc = getOpcodeGroup1(MOVW_WRITE_RN_OPCODE, dst, src);
        oneShortOp(opc);
    }

    void movwMemReg(RegisterID src, RegisterID dst)
    {
        uint16_t opc = getOpcodeGroup1(MOVW_READ_RM_OPCODE, dst, src);
        oneShortOp(opc);
    }

    void movwPCReg(int offset, RegisterID base, RegisterID dst)
    {
        ASSERT(base == SH4Registers::pc);
        ASSERT((offset <= 255) && (offset >= 0));

        uint16_t opc = getOpcodeGroup3(MOVW_READ_OFFPC_OPCODE, dst, offset);
        oneShortOp(opc);
    }

    void movwMemReg(int offset, RegisterID base, RegisterID dst)
    {
        ASSERT(dst == SH4Registers::r0);

        uint16_t opc = getOpcodeGroup11(MOVW_READ_OFFRM_OPCODE, base, offset);
        oneShortOp(opc);
    }

    void movwR0mr(RegisterID src, RegisterID dst)
    {
        uint16_t opc = getOpcodeGroup1(MOVW_READ_R0RM_OPCODE, dst, src);
        oneShortOp(opc);
    }

    void movlRegMem(RegisterID src, int offset, RegisterID base)
    {
        ASSERT((offset <= 15) && (offset >= 0));

        if (!offset) {
            oneShortOp(getOpcodeGroup1(MOVL_WRITE_RN_OPCODE, base, src));
            return;
        }

        oneShortOp(getOpcodeGroup4(MOVL_WRITE_OFFRN_OPCODE, base, src, offset));
    }

    void movlRegMem(RegisterID src, RegisterID base)
    {
        uint16_t opc = getOpcodeGroup1(MOVL_WRITE_RN_OPCODE, base, src);
        oneShortOp(opc);
    }

    void movlMemReg(int offset, RegisterID base, RegisterID dst)
    {
        if (base == SH4Registers::pc) {
            ASSERT((offset <= 255) && (offset >= 0));
            oneShortOp(getOpcodeGroup3(MOVL_READ_OFFPC_OPCODE, dst, offset));
            return;
        }

        ASSERT((offset <= 15) && (offset >= 0));
        if (!offset) {
            oneShortOp(getOpcodeGroup1(MOVL_READ_RM_OPCODE, dst, base));
            return;
        }

        oneShortOp(getOpcodeGroup4(MOVL_READ_OFFRM_OPCODE, dst, base, offset));
    }

    void movbMemReg(int offset, RegisterID base, RegisterID dst)
    {
        ASSERT(dst == SH4Registers::r0);

        uint16_t opc = getOpcodeGroup11(MOVB_READ_OFFRM_OPCODE, base, offset);
        oneShortOp(opc);
    }

    void movbR0mr(RegisterID src, RegisterID dst)
    {
        uint16_t opc = getOpcodeGroup1(MOVB_READ_R0RM_OPCODE, dst, src);
        oneShortOp(opc);
    }

    void movbMemReg(RegisterID src, RegisterID dst)
    {
        uint16_t opc = getOpcodeGroup1(MOVB_READ_RM_OPCODE, dst, src);
        oneShortOp(opc);
    }

    void movlMemReg(RegisterID base, RegisterID dst)
    {
        uint16_t opc = getOpcodeGroup1(MOVL_READ_RM_OPCODE, dst, base);
        oneShortOp(opc);
    }

    void movlMemRegIn(RegisterID base, RegisterID dst)
    {
        uint16_t opc = getOpcodeGroup1(MOVL_READ_RMINC_OPCODE, dst, base);
        oneShortOp(opc);
    }

    void movlR0mr(RegisterID src, RegisterID dst)
    {
        uint16_t opc = getOpcodeGroup1(MOVL_READ_R0RM_OPCODE, dst, src);
        oneShortOp(opc);
    }

    void movlRegMemr0(RegisterID src, RegisterID dst)
    {
        uint16_t opc = getOpcodeGroup1(MOVL_WRITE_R0RN_OPCODE, dst, src);
        oneShortOp(opc);
    }

    void movlImm8r(int imm8, RegisterID dst)
    {
        ASSERT((imm8 <= 127) && (imm8 >= -128));

        uint16_t opc = getOpcodeGroup3(MOVIMM_OPCODE, dst, imm8);
        oneShortOp(opc);
    }

    void loadConstant(uint32_t constant, RegisterID dst)
    {
        if (((int)constant <= 0x7f) && ((int)constant >= -0x80)) {
            movImm8(constant, dst);
            return;
        }

        uint16_t opc = getOpcodeGroup3(MOVIMM_OPCODE, dst, 0);

        m_buffer.ensureSpace(maxInstructionSize, sizeof(uint32_t));
        printInstr(getOpcodeGroup3(MOVIMM_OPCODE, dst, constant), m_buffer.codeSize());
        m_buffer.putShortWithConstantInt(opc, constant, true);
    }

    void loadConstantUnReusable(uint32_t constant, RegisterID dst, bool ensureSpace = false)
    {
        uint16_t opc = getOpcodeGroup3(MOVIMM_OPCODE, dst, 0);

        if (ensureSpace)
            m_buffer.ensureSpace(maxInstructionSize, sizeof(uint32_t));

        printInstr(getOpcodeGroup3(MOVIMM_OPCODE, dst, constant), m_buffer.codeSize());
        m_buffer.putShortWithConstantInt(opc, constant);
    }

    // Flow control

    AssemblerLabel call()
    {
        RegisterID scr = claimScratch();
        m_buffer.ensureSpace(maxInstructionSize + 4, sizeof(uint32_t));
        loadConstantUnReusable(0x0, scr);
        branch(JSR_OPCODE, scr);
        nop();
        releaseScratch(scr);
        return m_buffer.label();
    }

    AssemblerLabel call(RegisterID dst)
    {
        m_buffer.ensureSpace(maxInstructionSize + 2);
        branch(JSR_OPCODE, dst);
        nop();
        return m_buffer.label();
    }

    AssemblerLabel jmp()
    {
        RegisterID scr = claimScratch();
        m_buffer.ensureSpace(maxInstructionSize + 4, sizeof(uint32_t));
        AssemblerLabel label = m_buffer.label();
        loadConstantUnReusable(0x0, scr);
        branch(BRAF_OPCODE, scr);
        nop();
        releaseScratch(scr);
        return label;
    }

    AssemblerLabel jmp(RegisterID dst)
    {
        jmpReg(dst);
        return m_buffer.label();
    }

    void jmpReg(RegisterID dst)
    {
        m_buffer.ensureSpace(maxInstructionSize + 2);
        branch(JMP_OPCODE, dst);
        nop();
    }

    AssemblerLabel jne()
    {
        AssemblerLabel label = m_buffer.label();
        branch(BF_OPCODE, 0);
        return label;
    }

    AssemblerLabel je()
    {
        AssemblerLabel label = m_buffer.label();
        branch(BT_OPCODE, 0);
        return label;
    }

    void ret()
    {
        m_buffer.ensureSpace(maxInstructionSize + 2);
        oneShortOp(RTS_OPCODE, false);
    }

    AssemblerLabel label()
    {
        m_buffer.ensureSpaceForAnyOneInstruction();
        return m_buffer.label();
    }

    int sizeOfConstantPool()
    {
         return m_buffer.sizeOfConstantPool();
    }

    AssemblerLabel align(int alignment)
    {
        m_buffer.ensureSpace(maxInstructionSize + 2);
        while (!m_buffer.isAligned(alignment)) {
            nop();
            m_buffer.ensureSpace(maxInstructionSize + 2);
        }
        return label();
    }

    static void changePCrelativeAddress(int offset, uint16_t* instructionPtr, uint32_t newAddress)
    {
        uint32_t address = (offset << 2) + ((reinterpret_cast<uint32_t>(instructionPtr) + 4) &(~0x3));
        *reinterpret_cast<uint32_t*>(address) = newAddress;
    }
    
    static uint32_t readPCrelativeAddress(int offset, uint16_t* instructionPtr)
    {
        uint32_t address = (offset << 2) + ((reinterpret_cast<uint32_t>(instructionPtr) + 4) &(~0x3));
        return *reinterpret_cast<uint32_t*>(address);
    }

    static uint16_t* getInstructionPtr(void* code, int offset)
    {
        return reinterpret_cast<uint16_t*> (reinterpret_cast<uint32_t>(code) + offset);
    }

    static void linkJump(void* code, AssemblerLabel from, void* to)
    {
        ASSERT(from.isSet());

        uint16_t* instructionPtr = getInstructionPtr(code, from.m_offset);
        uint16_t instruction = *instructionPtr;
        int offsetBits = (reinterpret_cast<uint32_t>(to) - reinterpret_cast<uint32_t>(code)) - from.m_offset;

        if (((instruction & 0xff00) == BT_OPCODE) || ((instruction & 0xff00) == BF_OPCODE)) {
            /* BT label ==> BF 2
               nop          LDR reg
               nop          braf @reg
               nop          nop
            */
            offsetBits -= 8;
            instruction ^= 0x0202;
            *instructionPtr++ = instruction;
            changePCrelativeAddress((*instructionPtr & 0xff), instructionPtr, offsetBits);
            instruction = (BRAF_OPCODE | (*instructionPtr++ & 0xf00));
            *instructionPtr = instruction;
            printBlockInstr(instructionPtr - 2, from.m_offset, 3);
            return;
         }

         /* MOV #imm, reg => LDR reg
            braf @reg        braf @reg
            nop              nop
         */
        ASSERT((*(instructionPtr + 1) & BRAF_OPCODE) == BRAF_OPCODE);

        offsetBits -= 4;
        if (offsetBits >= -4096 && offsetBits <= 4094) {
            *instructionPtr = getOpcodeGroup6(BRA_OPCODE, offsetBits >> 1);
            *(++instructionPtr) = NOP_OPCODE;
            printBlockInstr(instructionPtr - 1, from.m_offset, 2);
            return;
        }

        changePCrelativeAddress((*instructionPtr & 0xff), instructionPtr, offsetBits - 2);
        printInstr(*instructionPtr, from.m_offset + 2);
    }

    static void linkCall(void* code, AssemblerLabel from, void* to)
    {
        uint16_t* instructionPtr = getInstructionPtr(code, from.m_offset);
        instructionPtr -= 3;
        changePCrelativeAddress((*instructionPtr & 0xff), instructionPtr, reinterpret_cast<uint32_t>(to));
    }

    static void linkPointer(void* code, AssemblerLabel where, void* value)
    {
        uint16_t* instructionPtr = getInstructionPtr(code, where.m_offset);
        changePCrelativeAddress((*instructionPtr & 0xff), instructionPtr, reinterpret_cast<uint32_t>(value));
    }

    static unsigned getCallReturnOffset(AssemblerLabel call)
    {
        ASSERT(call.isSet());
        return call.m_offset;
    }

    static uint32_t* getLdrImmAddressOnPool(SH4Word* insn, uint32_t* constPool)
    {
        return (constPool + (*insn & 0xff));
    }

    static SH4Word patchConstantPoolLoad(SH4Word load, int value)
    {
        return ((load & ~0xff) | value);
    }

    static SH4Buffer::TwoShorts placeConstantPoolBarrier(int offset)
    {
        ASSERT(((offset >> 1) <=2047) && ((offset >> 1) >= -2048));

        SH4Buffer::TwoShorts m_barrier;
        m_barrier.high = (BRA_OPCODE | (offset >> 1));
        m_barrier.low = NOP_OPCODE;
        printInstr(((BRA_OPCODE | (offset >> 1))), 0);
        printInstr(NOP_OPCODE, 0);
        return m_barrier;
    }

    static void patchConstantPoolLoad(void* loadAddr, void* constPoolAddr)
    {
        SH4Word* instructionPtr = reinterpret_cast<SH4Word*>(loadAddr);
        SH4Word instruction = *instructionPtr;
        SH4Word index = instruction & 0xff;

        if ((instruction & 0xf000) != MOVIMM_OPCODE)
            return;

        ASSERT((((reinterpret_cast<uint32_t>(constPoolAddr) - reinterpret_cast<uint32_t>(loadAddr)) + index * 4)) < 1024);

        int offset = reinterpret_cast<uint32_t>(constPoolAddr) + (index * 4) - ((reinterpret_cast<uint32_t>(instructionPtr) & ~0x03) + 4);
        instruction &=0xf00;
        instruction |= 0xd000;
        offset &= 0x03ff;
        instruction |= (offset >> 2);
        *instructionPtr = instruction;
        printInstr(instruction, reinterpret_cast<uint32_t>(loadAddr));
    }

    static void repatchPointer(void* where, void* value)
    {
        patchPointer(where, value);
    }

    static void* readPointer(void* code)
    {
        return static_cast<void*>(readInt32(code));
    }

    static void repatchInt32(void* where, int32_t value)
    {
        uint16_t* instructionPtr = reinterpret_cast<uint16_t*>(where);
        changePCrelativeAddress((*instructionPtr & 0xff), instructionPtr, value);
    }

    static void repatchCompact(void* where, int32_t value)
    {
        repatchInt32(where, value);
    }

    static void relinkCall(void* from, void* to)
    {
        uint16_t* instructionPtr = reinterpret_cast<uint16_t*>(from);
        instructionPtr -= 3;
        changePCrelativeAddress((*instructionPtr & 0xff), instructionPtr, reinterpret_cast<uint32_t>(to));
    }

    static void relinkJump(void* from, void* to)
    {
        uint16_t* instructionPtr = reinterpret_cast<uint16_t*> (from);
        uint16_t instruction = *instructionPtr;
        int32_t offsetBits = (reinterpret_cast<uint32_t>(to) - reinterpret_cast<uint32_t>(from));

        if (((*instructionPtr & 0xff00) == BT_OPCODE) || ((*instructionPtr & 0xff00) == BF_OPCODE)) {
            offsetBits -= 8;
            instructionPtr++;
            changePCrelativeAddress((*instructionPtr & 0xff), instructionPtr, offsetBits);
            instruction = (BRAF_OPCODE | (*instructionPtr++ & 0xf00));
            *instructionPtr = instruction;
            printBlockInstr(instructionPtr, reinterpret_cast<uint32_t>(from) + 1, 3);
            return;
        }

        ASSERT((*(instructionPtr + 1) & BRAF_OPCODE) == BRAF_OPCODE);
        offsetBits -= 4;
        if (offsetBits >= -4096 && offsetBits <= 4094) {
            *instructionPtr = getOpcodeGroup6(BRA_OPCODE, offsetBits >> 1);
            *(++instructionPtr) = NOP_OPCODE;
            printBlockInstr(instructionPtr - 2, reinterpret_cast<uint32_t>(from), 2);
            return;
        }

        changePCrelativeAddress((*instructionPtr & 0xff), instructionPtr, offsetBits - 2);
        printInstr(*instructionPtr, reinterpret_cast<uint32_t>(from));
    }

    // Linking & patching

    void linkJump(AssemblerLabel from, AssemblerLabel to)
    {
        ASSERT(to.isSet());
        ASSERT(from.isSet());

        uint16_t* instructionPtr = getInstructionPtr(data(), from.m_offset);
        uint16_t instruction = *instructionPtr;
        int offsetBits;

        if (((instruction & 0xff00) == BT_OPCODE) || ((instruction & 0xff00) == BF_OPCODE)) {
            /* BT label => BF 2
               nop        LDR reg
               nop        braf @reg
               nop        nop
            */
            offsetBits = (to.m_offset - from.m_offset) - 8;
            instruction ^= 0x0202;
            *instructionPtr++ = instruction;
            if ((*instructionPtr & 0xf000) == 0xe000) {
                uint32_t* addr = getLdrImmAddressOnPool(instructionPtr, m_buffer.poolAddress());
                *addr = offsetBits;
            } else
                changePCrelativeAddress((*instructionPtr & 0xff), instructionPtr, offsetBits);
            instruction = (BRAF_OPCODE | (*instructionPtr++ & 0xf00));
            *instructionPtr = instruction;
            printBlockInstr(instructionPtr - 2, from.m_offset, 3);
            return;
        }

        /* MOV # imm, reg => LDR reg
           braf @reg         braf @reg
           nop               nop
        */
        ASSERT((*(instructionPtr + 1) & BRAF_OPCODE) == BRAF_OPCODE);
        offsetBits = (to.m_offset - from.m_offset) - 4;
        if (offsetBits >= -4096 && offsetBits <= 4094) {
            *instructionPtr = getOpcodeGroup6(BRA_OPCODE, offsetBits >> 1);
            *(++instructionPtr) = NOP_OPCODE;
            printBlockInstr(instructionPtr - 1, from.m_offset, 2);
            return;
        }

        instruction = *instructionPtr;
        if ((instruction  & 0xf000) == 0xe000) {
            uint32_t* addr = getLdrImmAddressOnPool(instructionPtr, m_buffer.poolAddress());
            *addr = offsetBits - 2;
            printInstr(*instructionPtr, from.m_offset + 2);
            return;
        }

        changePCrelativeAddress((*instructionPtr & 0xff), instructionPtr, offsetBits - 2);
        printInstr(*instructionPtr, from.m_offset + 2);
    }

    static void* getRelocatedAddress(void* code, AssemblerLabel label)
    {
        return reinterpret_cast<void*>(reinterpret_cast<char*>(code) + label.m_offset);
    }

    static int getDifferenceBetweenLabels(AssemblerLabel a, AssemblerLabel b)
    {
        return b.m_offset - a.m_offset;
    }

    static void patchPointer(void* code, AssemblerLabel where, void* value)
    {
        patchPointer(reinterpret_cast<uint32_t*>(code) + where.m_offset, value);
    }

    static void patchPointer(void* code, void* value)
    {
        patchInt32(code, reinterpret_cast<uint32_t>(value));
    }

    static void patchInt32(void* code, uint32_t value)
    {
        changePCrelativeAddress((*(reinterpret_cast<uint16_t*>(code)) & 0xff), reinterpret_cast<uint16_t*>(code), value);
    }

    static uint32_t readInt32(void* code)
    {
        return readPCrelativeAddress((*(reinterpret_cast<uint16_t*>(code)) & 0xff), reinterpret_cast<uint16_t*>(code));
    }

    void* executableCopy(JSGlobalData& globalData, ExecutablePool* allocator)
    {
        return m_buffer.executableCopy(globalData, allocator);
    }

    void prefix(uint16_t pre)
    {
        m_buffer.putByte(pre);
    }

    void oneShortOp(uint16_t opcode, bool checksize = true, bool isDouble = true)
    {
        printInstr(opcode, m_buffer.codeSize(), isDouble);
        if (checksize)
            m_buffer.ensureSpace(maxInstructionSize);
        m_buffer.putShortUnchecked(opcode);
    }

    void ensureSpace(int space)
    {
        m_buffer.ensureSpace(space);
    }

    void ensureSpace(int insnSpace, int constSpace)
    {
        m_buffer.ensureSpace(insnSpace, constSpace);
    }

    // Administrative methods

    void* data() const { return m_buffer.data(); }
    size_t codeSize() const { return m_buffer.codeSize(); }

#ifdef SH4_ASSEMBLER_TRACING
    static void printInstr(uint16_t opc, unsigned int size, bool isdoubleInst = true)
    {
        if (!getenv("JavaScriptCoreDumpJIT"))
            return;

        const char *format = 0;
        printfStdoutInstr("offset: 0x%8.8x\t", size);
        switch (opc) {
        case BRK_OPCODE:
            format = "    BRK\n";
            break;
        case NOP_OPCODE:
            format = "    NOP\n";
            break;
        case RTS_OPCODE:
            format ="    *RTS\n";
            break;
        case SETS_OPCODE:
            format = "    SETS\n";
            break;
        case SETT_OPCODE:
            format = "    SETT\n";
            break;
        case CLRT_OPCODE:
            format = "    CLRT\n";
            break;
        case FSCHG_OPCODE:
            format = "    FSCHG\n";
            break;
        }
        if (format) {
            printfStdoutInstr(format);
            return;
        }
        switch (opc & 0xf0ff) {
        case BRAF_OPCODE:
            format = "    *BRAF R%d\n";
            break;
        case DT_OPCODE:
            format = "    DT R%d\n";
            break;
        case CMPPL_OPCODE:
            format = "    CMP/PL R%d\n";
            break;
        case CMPPZ_OPCODE:
            format = "    CMP/PZ R%d\n";
            break;
        case JMP_OPCODE:
            format = "    *JMP @R%d\n";
            break;
        case JSR_OPCODE:
            format = "    *JSR @R%d\n";
            break;
        case LDSPR_OPCODE:
            format = "    LDS R%d, PR\n";
            break;
        case LDSLPR_OPCODE:
            format = "    LDS.L @R%d+, PR\n";
            break;
        case MOVT_OPCODE:
            format = "    MOVT R%d\n";
            break;
        case SHAL_OPCODE:
            format = "    SHAL R%d\n";
            break;
        case SHAR_OPCODE:
            format = "    SHAR R%d\n";
            break;
        case SHLL_OPCODE:
            format = "    SHLL R%d\n";
            break;
        case SHLL2_OPCODE:
            format = "    SHLL2 R%d\n";
            break;
        case SHLL8_OPCODE:
            format = "    SHLL8 R%d\n";
            break;
        case SHLL16_OPCODE:
            format = "    SHLL16 R%d\n";
            break;
        case SHLR_OPCODE:
            format = "    SHLR R%d\n";
            break;
        case SHLR2_OPCODE:
            format = "    SHLR2 R%d\n";
            break;
        case SHLR8_OPCODE:
            format = "    SHLR8 R%d\n";
            break;
        case SHLR16_OPCODE:
            format = "    SHLR16 R%d\n";
            break;
        case STSPR_OPCODE:
            format = "    STS PR, R%d\n";
            break;
        case STSLPR_OPCODE:
            format = "    STS.L PR, @-R%d\n";
            break;
        case LDS_RM_FPUL_OPCODE:
            format = "    LDS R%d, FPUL\n";
            break;
        case STS_FPUL_RN_OPCODE:
            format = "    STS FPUL, R%d \n";
            break;
        case FLDS_FRM_FPUL_OPCODE:
            format = "    FLDS FR%d, FPUL\n";
            break;
        case FSTS_FPUL_FRN_OPCODE:
            format = "    FSTS FPUL, R%d \n";
            break;
        case LDSFPSCR_OPCODE:
            format = "    LDS R%d, FPSCR \n";
            break;
        case STSFPSCR_OPCODE:
            format = "    STS FPSCR, R%d \n";
            break;
        case STSMACL_OPCODE:
            format = "    STS MACL, R%d \n";
            break;
        case STSMACH_OPCODE:
            format = "    STS MACH, R%d \n";
            break;
        case BSRF_OPCODE:
            format = "    *BSRF R%d";
            break;
        case FTRC_OPCODE:
            format = "    FTRC FR%d, FPUL\n";
            break;
        }
        if (format) {
            printfStdoutInstr(format, getRn(opc));
            return;
        }
        switch (opc & 0xf0ff) {
        case FNEG_OPCODE:
            format = "    FNEG DR%d\n";
            break;
        case FLOAT_OPCODE:
            format = "    FLOAT DR%d\n";
            break;
        case FTRC_OPCODE:
            format = "    FTRC FR%d, FPUL\n";
            break;
        case FSQRT_OPCODE:
            format = "    FSQRT FR%d\n";
            break;
        case FCNVDS_DRM_FPUL_OPCODE:
            format = "    FCNVDS FR%d, FPUL\n";
            break;
        }
        if (format) {
            if (isdoubleInst)
                printfStdoutInstr(format, getDRn(opc) << 1);
            else
                printfStdoutInstr(format, getRn(opc));
            return;
        }
        switch (opc & 0xf00f) {
        case ADD_OPCODE:
            format = "    ADD R%d, R%d\n";
            break;
        case ADDC_OPCODE:
            format = "    ADDC R%d, R%d\n";
            break;
        case ADDV_OPCODE:
            format = "    ADDV R%d, R%d\n";
            break;
        case AND_OPCODE:
            format = "    AND R%d, R%d\n";
            break;
        case DIV1_OPCODE:
            format = "    DIV1 R%d, R%d\n";
            break;
        case CMPEQ_OPCODE:
            format = "    CMP/EQ R%d, R%d\n";
            break;
        case CMPGE_OPCODE:
            format = "    CMP/GE R%d, R%d\n";
            break;
        case CMPGT_OPCODE:
            format = "    CMP/GT R%d, R%d\n";
            break;
        case CMPHI_OPCODE:
            format = "    CMP/HI R%d, R%d\n";
            break;
        case CMPHS_OPCODE:
            format = "    CMP/HS R%d, R%d\n";
            break;
        case MOV_OPCODE:
            format = "    MOV R%d, R%d\n";
            break;
        case MOVB_WRITE_RN_OPCODE:
            format = "    MOV.B R%d, @R%d\n";
            break;
        case MOVB_WRITE_RNDEC_OPCODE:
            format = "    MOV.B R%d, @-R%d\n";
            break;
        case MOVB_WRITE_R0RN_OPCODE:
            format = "    MOV.B R%d, @(R0, R%d)\n";
            break;
        case MOVB_READ_RM_OPCODE:
            format = "    MOV.B @R%d, R%d\n";
            break;
        case MOVB_READ_RMINC_OPCODE:
            format = "    MOV.B @R%d+, R%d\n";
            break;
        case MOVB_READ_R0RM_OPCODE:
            format = "    MOV.B @(R0, R%d), R%d\n";
            break;
        case MOVL_WRITE_RN_OPCODE:
            format = "    MOV.L R%d, @R%d\n";
            break;
        case MOVL_WRITE_RNDEC_OPCODE:
            format = "    MOV.L R%d, @-R%d\n";
            break;
        case MOVL_WRITE_R0RN_OPCODE:
            format = "    MOV.L R%d, @(R0, R%d)\n";
            break;
        case MOVL_READ_RM_OPCODE:
            format = "    MOV.L @R%d, R%d\n";
            break;
        case MOVL_READ_RMINC_OPCODE:
            format = "    MOV.L @R%d+, R%d\n";
            break;
        case MOVL_READ_R0RM_OPCODE:
            format = "    MOV.L @(R0, R%d), R%d\n";
            break;
        case MULL_OPCODE:
            format = "    MUL.L R%d, R%d\n";
            break;
        case DMULL_L_OPCODE:
            format = "    DMULU.L R%d, R%d\n";
            break;
        case DMULSL_OPCODE:
            format = "    DMULS.L R%d, R%d\n";
            break;
        case NEG_OPCODE:
            format = "    NEG R%d, R%d\n";
            break;
        case NEGC_OPCODE:
            format = "    NEGC R%d, R%d\n";
            break;
        case NOT_OPCODE:
            format = "    NOT R%d, R%d\n";
            break;
        case OR_OPCODE:
            format = "    OR R%d, R%d\n";
            break;
        case SHAD_OPCODE:
            format = "    SHAD R%d, R%d\n";
            break;
        case SHLD_OPCODE:
            format = "    SHLD R%d, R%d\n";
            break;
        case SUB_OPCODE:
            format = "    SUB R%d, R%d\n";
            break;
        case SUBC_OPCODE:
            format = "    SUBC R%d, R%d\n";
            break;
        case SUBV_OPCODE:
            format = "    SUBV R%d, R%d\n";
            break;
        case TST_OPCODE:
            format = "    TST R%d, R%d\n";
            break;
        case XOR_OPCODE:
            format = "    XOR R%d, R%d\n";break;
        case MOVW_WRITE_RN_OPCODE:
            format = "    MOV.W R%d, @R%d\n";
            break;
        case MOVW_READ_RM_OPCODE:
            format = "    MOV.W @R%d, R%d\n";
            break;
        case MOVW_READ_R0RM_OPCODE:
            format = "    MOV.W @(R0, R%d), R%d\n";
            break;
        case EXTUW_OPCODE:
            format = "    EXTU.W R%d, R%d\n";
            break;
        }
        if (format) {
            printfStdoutInstr(format, getRm(opc), getRn(opc));
            return;
        }
        switch (opc & 0xf00f) {
        case FSUB_OPCODE:
            format = "    FSUB FR%d, FR%d\n";
            break;
        case FADD_OPCODE:
            format = "    FADD FR%d, FR%d\n";
            break;
        case FDIV_OPCODE:
            format = "    FDIV FR%d, FR%d\n";
            break;
        case FMUL_OPCODE:
            format = "    DMULL FR%d, FR%d\n";
            break;
        case FMOV_OPCODE:
            format = "    FMOV FR%d, FR%d\n";
            break;
        case FCMPEQ_OPCODE:
            format = "    FCMP/EQ FR%d, FR%d\n";
            break;
        case FCMPGT_OPCODE:
            format = "    FCMP/GT FR%d, FR%d\n";
            break;
        }
        if (format) {
            if (isdoubleInst)
                printfStdoutInstr(format, getDRm(opc) << 1, getDRn(opc) << 1);
            else
                printfStdoutInstr(format, getRm(opc), getRn(opc));
            return;
        }
        switch (opc & 0xf00f) {
        case FMOVS_WRITE_RN_DEC_OPCODE:
            format = "    %s FR%d, @-R%d\n";
            break;
        case FMOVS_WRITE_RN_OPCODE:
            format = "    %s FR%d, @R%d\n";
            break;
        case FMOVS_WRITE_R0RN_OPCODE:
            format = "    %s FR%d, @(R0, R%d)\n";
            break;
        }
        if (format) {
            if (isdoubleInst)
                printfStdoutInstr(format, "FMOV", getDRm(opc) << 1, getDRn(opc));
            else
                printfStdoutInstr(format, "FMOV.S", getRm(opc), getRn(opc));
            return;
        }
        switch (opc & 0xf00f) {
        case FMOVS_READ_RM_OPCODE:
            format = "    %s @R%d, FR%d\n";
            break;
        case FMOVS_READ_RM_INC_OPCODE:
            format = "    %s @R%d+, FR%d\n";
            break;
        case FMOVS_READ_R0RM_OPCODE:
            format = "    %s @(R0, R%d), FR%d\n";
            break;
        }
        if (format) {
            if (isdoubleInst)
                printfStdoutInstr(format, "FMOV", getDRm(opc), getDRn(opc) << 1);
            else
                printfStdoutInstr(format, "FMOV.S", getRm(opc), getRn(opc));
            return;
        }
        switch (opc & 0xff00) {
        case BF_OPCODE:
            format = "    BF %d\n";
            break;
        case BFS_OPCODE:
            format = "    *BF/S %d\n";
            break;
        case ANDIMM_OPCODE:
            format = "    AND #%d, R0\n";
            break;
        case BT_OPCODE:
            format = "    BT %d\n";
            break;
        case BTS_OPCODE:
            format = "    *BT/S %d\n";
            break;
        case CMPEQIMM_OPCODE:
            format = "    CMP/EQ #%d, R0\n";
            break;
        case MOVB_WRITE_OFFGBR_OPCODE:
            format = "    MOV.B R0, @(%d, GBR)\n";
            break;
        case MOVB_READ_OFFGBR_OPCODE:
            format = "    MOV.B @(%d, GBR), R0\n";
            break;
        case MOVL_WRITE_OFFGBR_OPCODE:
            format = "    MOV.L R0, @(%d, GBR)\n";
            break;
        case MOVL_READ_OFFGBR_OPCODE:
            format = "    MOV.L @(%d, GBR), R0\n";
            break;
        case MOVA_READ_OFFPC_OPCODE:
            format = "    MOVA @(%d, PC), R0\n";
            break;
        case ORIMM_OPCODE:
            format = "    OR #%d, R0\n";
            break;
        case ORBIMM_OPCODE:
            format = "    OR.B #%d, @(R0, GBR)\n";
            break;
        case TSTIMM_OPCODE:
            format = "    TST #%d, R0\n";
            break;
        case TSTB_OPCODE:
            format = "    TST.B %d, @(R0, GBR)\n";
            break;
        case XORIMM_OPCODE:
            format = "    XOR #%d, R0\n";
            break;
        case XORB_OPCODE:
            format = "    XOR.B %d, @(R0, GBR)\n";
            break;
        }
        if (format) {
            printfStdoutInstr(format, getImm8(opc));
            return;
        }
        switch (opc & 0xff00) {
        case MOVB_WRITE_OFFRN_OPCODE:
            format = "    MOV.B R0, @(%d, R%d)\n";
            break;
        case MOVB_READ_OFFRM_OPCODE:
            format = "    MOV.B @(%d, R%d), R0\n";
            break;
        }
        if (format) {
            printfStdoutInstr(format, getDisp(opc), getRm(opc));
            return;
        }
        switch (opc & 0xf000) {
        case BRA_OPCODE:
            format = "    *BRA %d\n";
            break;
        case BSR_OPCODE:
            format = "    *BSR %d\n";
            break;
        }
        if (format) {
            printfStdoutInstr(format, getImm12(opc));
            return;
        }
        switch (opc & 0xf000) {
        case MOVL_READ_OFFPC_OPCODE:
            format = "    MOV.L @(%d, PC), R%d\n";
            break;
        case ADDIMM_OPCODE:
            format = "    ADD #%d, R%d\n";
            break;
        case MOVIMM_OPCODE:
            format = "    MOV #%d, R%d\n";
            break;
        case MOVW_READ_OFFPC_OPCODE:
            format = "    MOV.W @(%d, PC), R%d\n";
            break;
        }
        if (format) {
            printfStdoutInstr(format, getImm8(opc), getRn(opc));
            return;
        }
        switch (opc & 0xf000) {
        case MOVL_WRITE_OFFRN_OPCODE:
            format = "    MOV.L R%d, @(%d, R%d)\n";
            printfStdoutInstr(format, getRm(opc), getDisp(opc), getRn(opc));
            break;
        case MOVL_READ_OFFRM_OPCODE:
            format = "    MOV.L @(%d, R%d), R%d\n";
            printfStdoutInstr(format, getDisp(opc), getRm(opc), getRn(opc));
            break;
        }
    }

    static void printfStdoutInstr(const char* format, ...)
    {
        if (getenv("JavaScriptCoreDumpJIT")) {
            va_list args;
            va_start(args, format);
            vprintfStdoutInstr(format, args);
            va_end(args);
        }
    }

    static void vprintfStdoutInstr(const char* format, va_list args)
    {
        if (getenv("JavaScriptCoreDumpJIT"))
            vfprintf(stdout, format, args);
    }

    static void printBlockInstr(uint16_t* first, unsigned int offset, int nbInstr)
    {
        printfStdoutInstr(">> repatch instructions after link\n");
        for (int i = 0; i <= nbInstr; i++)
           printInstr(*(first + i), offset + i);
        printfStdoutInstr(">> end repatch\n");
    }
#else
    static void printInstr(uint16_t opc, unsigned int size, bool isdoubleInst = true) {};
    static void printBlockInstr(uint16_t* first, unsigned int offset, int nbInstr) {};
#endif

private:
    SH4Buffer m_buffer;
    int m_claimscratchReg;
};

} // namespace JSC

#endif // ENABLE(ASSEMBLER) && CPU(SH4)

#endif // SH4Assembler_h
