//
// Copyright (C) 2010 Wayne Meissner
// Copyright (c) 2008-2009, Petr Kobalicek <kobalicek.petr@gmail.com>
//
// Permission is hereby granted, free of charge, to any person
// obtaining a copy of this software and associated documentation
// files (the "Software"), to deal in the Software without
// restriction, including without limitation the rights to use,
// copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following
// conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
// OTHER DEALINGS IN THE SOFTWARE.

package com.kenai.jnr.x86asm;

import java.nio.ByteBuffer;
import java.util.LinkedList;
import java.util.List;
import static com.kenai.jnr.x86asm.HINT.*;
import static com.kenai.jnr.x86asm.INST_CODE.*;
import static com.kenai.jnr.x86asm.InstructionGroup.*;
import static com.kenai.jnr.x86asm.OP.*;
import static com.kenai.jnr.x86asm.OperandFlags.*;
import static com.kenai.jnr.x86asm.PROPERTY.*;
import static com.kenai.jnr.x86asm.RELOC_MODE.*;
import static com.kenai.jnr.x86asm.REG.*;

import static com.kenai.jnr.x86asm.Util.*;


/**
 * Low level code generation.
 */
@Deprecated
public final class Assembler extends Serializer {
    private final CodeBuffer _buffer = new CodeBuffer();
    private final List<RelocData> _relocData = new LinkedList<RelocData>();
    private final CpuInfo cpuInfo = CpuInfo.GENERIC;
    private int _properties = 0;

    /** Size of possible trampolines. */
    int _trampolineSize;

    private final Logger _logger = null;

    private final CPU cpu;

    @Override
    boolean is64() {
        return cpu == CPU.X86_64;
    }

    private static final int intValue(boolean b) {
        return b ? 1 : 0;
    }

    public static final CPU I386 = CPU.I386;
    public static final CPU X86_64 = CPU.X86_64;

    public Assembler(CPU cpu) {
        this.cpu = cpu;
    }
    
    public final int offset() {
        return _buffer.offset();
    }

    /** Gets the required size of memory required to store all the generated code */
    public final int codeSize() {
        return _buffer.offset() + trampolineSize();
    }

    /** Return size of all possible trampolines needed to successfuly generate
     * relative jumps to absolute addresses. This value is only non-zero if jmp
     * of call instructions were used with immediate operand (this means jump or
     * call absolute address directly).
     *
     * Currently only _emitJmpOrCallReloc() method can increase trampoline size
     * value.
     */
    int trampolineSize() {
        return _trampolineSize;
    }
    
    //! @brief Set byte at position @a pos.
    public final byte getByteAt(int pos) {
        return _buffer.getByteAt(pos);
    }

    //! @brief Set word at position @a pos.
    public final short getWordAt(int pos) {
        return _buffer.getWordAt(pos);
    }

    //! @brief Set word at position @a pos.
    public final int getDWordAt(int pos) {
        return _buffer.getDWordAt(pos);
    }

    //! @brief Set word at position @a pos.
    public final long getQWordAt(int pos) {
        return _buffer.getQWordAt(pos);
    }

    //! @brief Set byte at position @a pos.
    public final void setByteAt(int pos, byte x) {
        _buffer.setByteAt(pos, x);
    }

    //! @brief Set word at position @a pos.
    public final void setWordAt(int pos, short x) {
        _buffer.setWordAt(pos, x);
    }

    //! @brief Set word at position @a pos.
    public final void setDWordAt(int pos, int x) {
        _buffer.setDWordAt(pos, x);
    }

    //! @brief Set word at position @a pos.
    public final void setQWordAt(int pos, long x) {
        _buffer.setQWordAt(pos, x);
    }

    //! @brief Set word at position @a pos.
    public final int getInt32At(int pos) {
        return (int) _buffer.getDWordAt(pos);
    }

    //! @brief Set int32 at position @a pos.
    public final void setInt32At(int pos, long x) {
        _buffer.setDWordAt(pos, (int) x);
    }

    public final void setVarAt(int pos, long i, boolean isUnsigned, int size) {
        switch (size) {
            case 1:
                setByteAt(pos, (byte) i);
                break;
            case 2:
                setWordAt(pos, (short) i);
                break;
            case 4:
                setDWordAt(pos, (int) i);
                break;
            case 8:
                setQWordAt(pos, i);
            default:
                throw new IllegalArgumentException("invalid size");
        }
    }

    /** Emit Byte to internal buffer. */
    final void _emitByte(int  x) {
        _buffer.emitByte((byte) x);
    }

    /** Emit Word (2 bytes) to internal buffer. */
    final void _emitWord(int x) {
        _buffer.emitWord((short) x);
    }

    /** Emit DWord (4 bytes) to internal buffer. */
    final void _emitDWord(int x) {
        _buffer.emitDWord(x);
    }

    /** Emit QWord (8 bytes) to internal buffer. */
    final void _emitQWord(long x) {
        _buffer.emitQWord(x);
    }

    /** Emit Int32 (4 bytes) to internal buffer. */
    final void _emitInt32(int x) {
        _buffer.emitDWord(x);
    }

    /** Emit system signed integer (4 or 8 bytes) to internal buffer. */
    final void _emitSysInt(long x) {
        if (is64()) {
            _buffer.emitQWord(x);
        } else {
            _buffer.emitDWord((int) x);
        }
    }

    //! @brief Emit single @a opCode without operands.
    final void _emitOpCode(int opCode) {
        // instruction prefix
        if ((opCode & 0xFF000000) != 0) {
            _emitByte((byte) ((opCode & 0xFF000000) >> 24));
        }
        // instruction opcodes
        if ((opCode & 0x00FF0000) != 0) {
            _emitByte((byte) ((opCode & 0x00FF0000) >> 16));
        }
        if ((opCode & 0x0000FF00) != 0) {
            _emitByte((byte) ((opCode & 0x0000FF00) >> 8));
        }
        // last opcode is always emitted (can be also 0x00)
        _emitByte((byte) (opCode & 0x000000FF));
    }
    
    void _emitSegmentPrefix(Operand rm) {
        if (rm.isMem()) {
            SEGMENT segmentPrefix = ((Mem) rm).segmentPrefix();
            if (segmentPrefix != SEGMENT.SEGMENT_NONE) {
                _emitByte(segmentPrefix.prefix());
            }
        }
    }

    void _emitImmediate(Immediate imm, int size) {
        switch (size) {
            case 1:
                _emitByte(imm.byteValue());
                break;
            case 2:
                _emitWord(imm.shortValue());
                break;
            case 4:
                _emitDWord(imm.intValue());
                break;
            case 8:
                if (!is64()) {
                    throw new IllegalArgumentException("64 bit immediate values not supported for 32bit");
                }
                _emitQWord(imm.longValue());
                break;
            default:
                throw new IllegalArgumentException("invalid immediate operand size");
        }
    }

    /** Emit REX prefix (64 bit mode only). */
    void _emitRexR(int w, int opReg, int regCode) {
        if (is64()) {

            boolean r = (opReg & 0x8) != 0;
            boolean b = (regCode & 0x8) != 0;

            // w Default operand size(0=Default, 1=64 bits).
            // r Register field (1=high bit extension of the ModR/M REG field).
            // x Index field not used in RexR
            // b Base field (1=high bit extension of the ModR/M or SIB Base field).
            if (w != 0 || r || b || (_properties & (1 << PROPERTY_X86_FORCE_REX)) != 0) {
                _emitByte(0x40 | (w << 3) | (intValue(r) << 2) | intValue(b));
            }
        }
    }
    
    void _emitRexR(boolean w, int opReg, int regCode) {
        _emitRexR(intValue(w), opReg, regCode);
    }

    /** Emit REX prefix (64 bit mode only). */
    void _emitRexRM(int w, int opReg, Operand rm) {
        if (is64()) {

            boolean r = (opReg & 0x8) != 0;
            boolean x = false;
            boolean b = false;

            if (rm.isReg()) {
                b = (((BaseReg) rm).code() & 0x8) != 0;

            } else if (rm.isMem()) {
                x = (((Mem) rm).index() & 0x8) != 0 && ((Mem) rm).index() != NO_REG;
                b = (((Mem) rm).base() & 0x8) != 0 && ((Mem) rm).base() != NO_REG;
            }

            // w Default operand size(0=Default, 1=64 bits).
            // r Register field (1=high bit extension of the ModR/M REG field).
            // x Index field (1=high bit extension of the SIB Index field).
            // b Base field (1=high bit extension of the ModR/M or SIB Base field).
            if (w != 0 || r || x || b || (_properties & (1 << PROPERTY_X86_FORCE_REX)) != 0) {
                _emitByte(0x40 | (w << 3) | (intValue(r) << 2) | (intValue(x) << 1) | intValue(b));
            }
        }
    }


    void _emitRexRM(boolean w, int opReg, Operand rm) {
        _emitRexRM(intValue(w), opReg, rm);
    }


    void _emitModM(int opReg, Mem mem, int immSize) {
        assert (mem.op() == OP_MEM);

        int baseReg = mem.base() & 0x7;
        int indexReg = mem.index() & 0x7;
        long disp = mem.displacement();
        int shift = mem.shift();

        // [base + displacemnt]
        if (mem.hasBase() && !mem.hasIndex()) {
            // ESP/RSP/R12 == 4
            if (baseReg == 4) {
                int mod = 0;

                if (disp != 0) {
                    mod = isInt8(disp) ? 1 : 2;
                }

                _emitMod(mod, opReg, 4);
                _emitSib(0, 4, 4);

                if (disp != 0) {
                    if (isInt8(disp)) {
                        _emitByte((byte) disp);
                    } else {
                        _emitInt32((int) disp);
                    }
                }
            } // EBP/RBP/R13 == 5
            else if (baseReg != 5 && disp == 0) {
                _emitMod(0, opReg, baseReg);
            } else if (isInt8(disp)) {
                _emitMod(1, opReg, baseReg);
                _emitByte((byte) disp);
            } else {
                _emitMod(2, opReg, baseReg);
                _emitInt32((int) disp);
            }
        }

        // [base + index * scale + displacemnt]
        else if (mem.hasBase() && mem.hasIndex()) {
            // ASMJIT_ASSERT(indexReg != RID_ESP);

            // EBP/RBP/R13 == 5
            if (baseReg != 5 && disp == 0) {
                _emitMod(0, opReg, 4);
                _emitSib(shift, indexReg, baseReg);
            } else if (isInt8(disp)) {
                _emitMod(1, opReg, 4);
                _emitSib(shift, indexReg, baseReg);
                _emitByte((byte) disp);
            } else {
                _emitMod(2, opReg, 4);
                _emitSib(shift, indexReg, baseReg);
                _emitInt32((int) disp);
            }
        }

        // Address                       | 32-bit mode | 64-bit mode
        // ------------------------------+-------------+---------------
        // [displacement]                |   ABSOLUTE  | RELATIVE (RIP)
        // [index * scale + displacemnt] |   ABSOLUTE  | ABSOLUTE (ZERO EXTENDED)
        else {
            // In 32 bit mode is used absolute addressing model.
            // In 64 bit mode is used relative addressing model together with absolute
            // addressing one. Main problem is that if instruction contains SIB then
            // relative addressing (RIP) is not possible.
            if (!is64()) {

                if (mem.hasIndex()) {
                    // ASMJIT_ASSERT(mem.index() != 4); // ESP/RSP == 4
                    _emitMod(0, opReg, 4);
                    _emitSib(shift, indexReg, 5);
                } else {
                    _emitMod(0, opReg, 5);
                }

                // X86 uses absolute addressing model, all relative addresses will be
                // relocated to absolute ones.
                if (mem.hasLabel()) {
                    Label label = mem.label();
                    int relocId = _relocData.size();


                    long destination = disp;
                    if (label.isBound()) {
                        destination += label.position();
                        // Dummy DWORD
                        _emitInt32(0);
                    } else {
                        _emitDisplacement(label, -4 - immSize, 4).relocId = relocId;
                    }

                    // Relative addressing will be relocated to absolute address.
                    RelocData rd = new RelocData(RelocData.Type.RELATIVE_TO_ABSOLUTE, 4, offset(), destination);

                    _relocData.add(rd);
                } else {
                    // Absolute address
                    _emitInt32((int) (mem.target() + disp));
                }

            } else {

                // X64 uses relative addressing model
                if (mem.hasLabel()) {
                    Label label = mem.label();

                    if (mem.hasIndex()) {
                        // Indexing is not possible
                        throw new IllegalArgumentException("illegal addressing");
                    }

                    // Relative address (RIP +/- displacement)
                    _emitMod(0, opReg, 5);

                    disp -= (4 + immSize);

                    if (label.isBound()) {
                        disp += offset() - label.position();
                        _emitInt32((int) disp);
                    } else {
                        _emitDisplacement(label, disp, 4);
                    }
                } else {
                    // Absolute address (truncated to 32 bits), this kind of address requires
                    // SIB byte (4)
                    _emitMod(0, opReg, 4);

                    if (mem.hasIndex()) {
                        // ASMJIT_ASSERT(mem.index() != 4); // ESP/RSP == 4
                        _emitSib(shift, indexReg, 5);
                    } else {
                        _emitSib(0, 4, 5);
                    }

                    // truncate to 32 bits
                    long target = mem.target() + disp;

                    if (target > 0xFFFFFFFFL) {
                        _logger.log("; Warning: Absolute address truncated to 32 bits\n");
                    }

                    _emitInt32((int) target);
                }

            }

        }
    }

    void _emitX86Inl(int opCode, boolean i16bit, boolean rexw, int reg) {
        _emitX86Inl(opCode, i16bit, intValue(rexw), reg);
    }

    void _emitX86Inl(int opCode, boolean i16bit, int rexw, int reg) {
        // 16 bit prefix
        if (i16bit) {
            _emitByte(0x66);
        }

        // instruction prefix
        if ((opCode & 0xFF000000) != 0) {
            _emitByte(((opCode & 0xFF000000) >> 24));
        }

        // rex prefix
        if (is64()) {
            _emitRexR(rexw, 0, reg);
        }


        // instruction opcodes
        if ((opCode & 0x00FF0000) != 0) {
            _emitByte(((opCode & 0x00FF0000) >> 16));
        }
        if ((opCode & 0x0000FF00) != 0) {
            _emitByte(((opCode & 0x0000FF00) >> 8));
        }

        _emitByte((opCode & 0x000000FF) + (reg & 0x7));
    }
    
    void _emitModRM(int opReg, Operand op, int immSize) {
        assert (op.op() == OP_REG || op.op() == OP_MEM);

        if (op.op() == OP_REG) {
            _emitModR(opReg, ((BaseReg) op).code());
        } else {
            _emitModM(opReg, (Mem) op, immSize);
        }
    }

    /** Emit MODR/M byte. */
    void _emitMod(int m, int o, int r) {
        _emitByte((byte) (((m & 0x03) << 6) | ((o & 0x07) << 3) | (r & 0x07)));
    }

    /** Emit SIB byte. */
    void _emitSib(int s, int i, int b) {
        _emitByte((byte) (((s & 0x03) << 6) | ((i & 0x07) << 3) | (b & 0x07)));
    }

    /** Emit Register / Register - calls _emitMod(3, opReg, r) */
    void _emitModR(int opReg, int r) {
        _emitMod(3, opReg, r);
    }

    /** Emit Register / Register - calls _emitMod(3, opReg, r.code()) */
    void _emitModR(int opReg, BaseReg r) {
        _emitMod(3, opReg, r.code());
    }

    void _emitX86RM(int opCode, boolean i16bit, boolean rexw, int o, Operand op, int immSize) {
        _emitX86RM(opCode, i16bit, intValue(rexw), o, op, immSize);
    }
    
    void _emitX86RM(int opCode, boolean i16bit, int rexw, int o, Operand op, int immSize) {
        // 16 bit prefix
        if (i16bit) {
            _emitByte(0x66);
        }

        // segment prefix
        _emitSegmentPrefix(op);

        // instruction prefix
        if ((opCode & 0xFF000000) != 0) {
            _emitByte(((opCode & 0xFF000000) >> 24));
        }

        // rex prefix
        if (is64()) {
            _emitRexRM(rexw, o, op);
        }

        // instruction opcodes
        if ((opCode & 0x00FF0000) != 0) {
            _emitByte((byte) ((opCode & 0x00FF0000) >> 16));
        }

        if ((opCode & 0x0000FF00) != 0) {
            _emitByte((byte) ((opCode & 0x0000FF00) >> 8));
        }

        _emitByte((byte) (opCode & 0x000000FF));

        // ModR/M
        _emitModRM(o, op, immSize);
    }
    
  void _emitX86(INST_CODE code, Operand o1, Operand o2, Operand o3) {
        
    InstructionDescription id = InstructionDescription.find(code);
    switch (id.group) {
    case I_EMIT:
    {
      _emitOpCode(id.opCode1);
      return;
    }

    case I_ALU:
    {
      int opCode = id.opCode1;
      int opReg = id.opCodeR;

      // Mem <- Reg
      if (o1.isMem() && o2.isReg())
      {
        _emitX86RM(opCode + intValue(!o2.isRegType(REG_GPB)),
          o2.isRegType(REG_GPW),
          o2.isRegType(REG_GPQ),
          ((Register) o2).code(),
          o1,
          0);
        return;
      }

      // Reg <- Reg|Mem
      if (o1.isReg() && o2.isRegMem())
      {
        _emitX86RM(opCode + 2 + intValue(!o1.isRegType(REG_GPB)),
          o1.isRegType(REG_GPW),
          o1.isRegType(REG_GPQ),
          ((Register) o1).code(),
          o2,
          0);
        return;
      }

      // AL, AX, EAX, RAX register shortcuts
      if (o1.isRegIndex(0) && o2.isImm())
      {
        if (o1.isRegType(REG_GPW))
          _emitByte(0x66); // 16 bit
        else if (o1.isRegType(REG_GPQ))
          _emitByte(0x48); // REX.W

        _emitByte((opReg << 3) | (0x04 + intValue(!o1.isRegType(REG_GPB))));
        _emitImmediate(
          (Immediate) o2, o1.size() <= 4 ? o1.size() : 4);
        return;
      }

      if (o1.isRegMem() && o2.isImm())
      {
        final Immediate imm = (Immediate) o2;
        int immSize = Util.isInt8(imm.value()) ? 1 : (o1.size() <= 4 ? o1.size() : 4);

        _emitX86RM(id.opCode2 + (o1.size() != 1 ? (immSize != 1 ? 1 : 3) : 0),
          o1.size() == 2,
          o1.size() == 8,
          opReg, o1,
          immSize);
        _emitImmediate(
          (Immediate) o2,
          immSize);
        return;
      }

      break;
    }

    case I_BSWAP:
    {
      if (o1.isReg())
      {
        final Register dst = (Register) o1;

        if (is64()) {
            _emitRexR(dst.type() == REG_GPQ, 1, dst.code());
        }

        _emitByte(0x0F);
        _emitModR(1, dst.code());
        return;
      }

      break;
    }

    case I_BT:
    {
      if (o1.isRegMem() && o2.isReg())
      {
        final Operand dst = o1;
        final Register src = (Register) o2;

        _emitX86RM(id.opCode1,
          src.isRegType(REG_GPW),
          src.isRegType(REG_GPQ),
          src.code(),
          dst,
          0);
        return;
      }

      if (o1.isRegMem() && o2.isImm())
      {
        final Operand dst = o1;
        final Immediate src = (Immediate) o2;

        _emitX86RM(id.opCode2,
          src.size() == 2,
          src.size() == 8,
          id.opCodeR,
          dst,
          1);
        _emitImmediate(src, 1);
        return;
      }

      break;
    }

    case I_CALL:
    {
      if (o1.isRegMem(is64() ? REG_GPQ : REG_GPD))
      {
        final Operand dst = o1;
        _emitX86RM(0xFF,
          false,
          false, 2, dst,
          0);
        return;
      }

      if (o1.isImm())
      {
        final Immediate imm = (Immediate) o1;
        _emitByte(0xE8);
        _emitJmpOrCallReloc(I_CALL, imm.value());
        return;
      }

      if (o1.isLabel())
      {
        Label label = (Label) o1;

        if (label.isBound())
        {
          final int rel32_size = 5;
          final int offs = label.position() - offset();
          assert(offs <= 0);

          _emitByte(0xE8);
          _emitInt32((int)(offs - rel32_size));
        }
        else
        {
          _emitByte(0xE8);
          _emitDisplacement(label, -4, 4);
        }
        return;
      }

      break;
    }

    case I_CRC32:
    {
      if (o1.isReg() && o2.isRegMem())
      {
        final Register dst = (Register) o1;
        final Operand src = o2;
        assert(dst.type() == REG_GPD || dst.type() == REG_GPQ);

        _emitX86RM(id.opCode1 + intValue(src.size() != 1),
          src.size() == 2,
          dst.type() == 8, dst.code(), src,
          0);
        return;
      }

      break;
    }

    case I_ENTER:
    {
      if (o1.isImm() && o2.isImm())
      {
        _emitByte(0xC8);
        _emitImmediate((Immediate) o1, 2);
        _emitImmediate((Immediate) o2, 1);
      }
      break;
    }

    case I_IMUL:
    {
      // 1 operand
      if (o1.isRegMem() && o2.isNone() && o3.isNone())
      {
        final Operand src = o1;
        _emitX86RM(0xF6 + intValue(src.size() != 1),
          src.size() == 2,
          src.size() == 8, 5, src,
          0);
        return;
      }
      // 2 operands
      else if (o1.isReg() && !o2.isNone() && o3.isNone())
      {
        final Register dst = (Register) o1;
        assert(!dst.isRegType(REG_GPW));

        if (o2.isRegMem())
        {
          final Operand src = o2;

          _emitX86RM(0x0FAF,
            dst.isRegType(REG_GPW),
            dst.isRegType(REG_GPQ), dst.code(), src,
            0);
          return;
        }
        else if (o2.isImm())
        {
          final Immediate imm = (Immediate) o2;

          if (isInt8(imm.value()) && imm.relocMode() == RELOC_NONE)
          {
            _emitX86RM(0x6B,
              dst.isRegType(REG_GPW),
              dst.isRegType(REG_GPQ), dst.code(), dst,
              1);
            _emitImmediate(imm, 1);
          }
          else
          {
            int immSize = dst.isRegType(REG_GPW) ? 2 : 4;
            _emitX86RM(0x69,
              dst.isRegType(REG_GPW),
              dst.isRegType(REG_GPQ), dst.code(), dst,
              immSize);
            _emitImmediate(imm, immSize);
          }
          return;
        }
      }
      // 3 operands
      else if (o1.isReg() && o2.isRegMem() && o3.isImm())
      {
        final Register dst = (Register) o1;
        final Operand src = o2;
        final Immediate imm = (Immediate) o3;

        if (isInt8(imm.value()) && imm.relocMode() == RELOC_NONE)
        {
          _emitX86RM(0x6B,
            dst.isRegType(REG_GPW),
            dst.isRegType(REG_GPQ), dst.code(), src,
            1);
          _emitImmediate(imm, 1);
        }
        else
        {
          int immSize = dst.isRegType(REG_GPW) ? 2 : 4;
          _emitX86RM(0x69,
            dst.isRegType(REG_GPW),
            dst.isRegType(REG_GPQ), dst.code(), src,
            immSize);
          _emitImmediate(imm, immSize);
        }
        return;
      }

      break;
    }

    case I_INC_DEC:
    {
      if (o1.isRegMem())
      {
        final Operand dst = o1;

        // INC [r16|r32] in 64 bit mode is not encodable.
        if (!is64() && dst.isReg() && (dst.isRegType(REG_GPW) || dst.isRegType(REG_GPD)))
        {
          _emitX86Inl(id.opCode1,
            dst.isRegType(REG_GPW),
            0, ((BaseReg) dst).code());
          return;
        }

        _emitX86RM(id.opCode2 + intValue(dst.size() != 1),
          dst.size() == 2,
          dst.size() == 8, id.opCodeR, dst,
          0);
        return;
      }

      break;
    }

    case I_J:
    {
      if (o1.isLabel())
      {
        Label label = (Label) o1;
        
        boolean isShortJump = (code.ordinal() >= INST_J_SHORT.ordinal() && code.ordinal() <= INST_JMP_SHORT.ordinal());

        final HINT hint = o2.isImm() ? HINT.valueOf((int) ((Immediate) o2).value()) : HINT.HINT_NONE;
        

        // Emit jump hint if configured for that.
        if (hint == HINT_TAKEN || hint == HINT_NOT_TAKEN
            && (_properties & (1 << PROPERTY_X86_JCC_HINTS)) != 0)
        {
            _emitByte(hint.value());
        }

        if (label.isBound())
        {
          final int rel8_size = 2;
          final int rel32_size = 6;
          final int offs = label.position() - offset();

          assert(offs <= 0);

          if (isInt8(offs - rel8_size))
          {
            _emitByte(0x70 | (id.opCode1 & 0xff));
            _emitByte((byte) (offs - rel8_size));
          }
          else
          {
            if (isShortJump && _logger != null)
            {
              _logger.log("; WARNING: Emitting long conditional jump, but short jump instruction forced!");
            }

            _emitByte(0x0F);
            _emitByte(0x80 | (id.opCode1 & 0xff));
            _emitInt32((int) (offs - rel32_size));
          }
        }
        else
        {
          if (isShortJump)
          {
            _emitByte(0x70 | (id.opCode1 & 0xff));
            _emitDisplacement(label, -1, 1);
          }
          else
          {
            _emitByte(0x0F);
            _emitByte(0x80 | (id.opCode1 & 0xff));
            _emitDisplacement(label, -4, 4);
          }
        }
        return;
      }

      break;
    }

    case I_JMP:
    {
      if (o1.isRegMem())
      {
        final Operand dst = o1;

        _emitX86RM(0xFF,
          false,
          false, 4, dst,
          0);
        return;
      }

      if (o1.isImm())
      {
        final Immediate imm = (Immediate) o1;
        _emitByte(0xE9);
        _emitJmpOrCallReloc(I_JMP, imm.value());
        return;
      }

      if (o1.isLabel())
      {
        Label label = (Label) o1;
        boolean isShortJump = (code == INST_JMP_SHORT);

        if (label.isBound())
        {
          final int rel8_size = 2;
          final int rel32_size = 5;
          final int offs = label.position() - offset();

          if (isInt8(offs - rel8_size))
          {
            _emitByte(0xEB);
            _emitByte((byte) (offs - rel8_size));
          }
          else
          {
            if (isShortJump && _logger != null)
            {
              _logger.log("; WARNING: Emitting long jump, but short jump instruction forced!");
            }

            _emitByte(0xE9);
            _emitInt32((int)(offs - rel32_size));
          }
        }
        else
        {
          if (isShortJump)
          {
            _emitByte(0xEB);
            _emitDisplacement(label, -1, 1);
          }
          else
          {
            _emitByte(0xE9);
            _emitDisplacement(label, -4, 4);
          }
        }
        return;
      }

      break;
    }

    case I_LEA:
    {
      if (o1.isReg() && o2.isMem())
      {
        final Register dst = (Register) o1;
        final Mem src = (Mem) o2;
        _emitX86RM(0x8D,
          dst.isRegType(REG_GPW),
          dst.isRegType(REG_GPQ), dst.code(), src,
          0);
        return;
      }

      break;
    }

    case I_M:
    {
      if (o1.isMem())
      {
        _emitX86RM(id.opCode1, false, (byte) id.opCode2, id.opCodeR, (Mem) o1, 0);
        return;
      }
      break;
    }

    case I_MOV:
    {
      final Operand dst = o1;
      final Operand src = o2;

      switch (dst.op() << 4 | src.op())
      {
        // Reg <- Reg/Mem
        case (OP_REG << 4) | OP_REG:
        {
          assert(src.isRegType(REG_GPB) || src.isRegType(REG_GPW) ||
                        src.isRegType(REG_GPD) || src.isRegType(REG_GPQ));
          // ... fall through ...
        }
        case (OP_REG << 4) | OP_MEM:
        {
          assert(dst.isRegType(REG_GPB) || dst.isRegType(REG_GPW) ||
                        dst.isRegType(REG_GPD) || dst.isRegType(REG_GPQ));

          _emitX86RM(0x0000008A + intValue(!dst.isRegType(REG_GPB)),
            dst.isRegType(REG_GPW),
            dst.isRegType(REG_GPQ),
            ((Register) dst).code(),
            src,
            0);
          return;
        }

        // Reg <- Imm
        case (OP_REG << 4) | OP_IMM:
        {
          final Immediate isrc = (Immediate) o2;

          // in 64 bit mode immediate can be 8 byte long!
          int immSize = dst.size();

          // Optimize instruction size by using 32 bit immediate if value can
          // fit to it
          if (is64() && immSize == 8 && isInt32(isrc.value()) && isrc.relocMode() == RELOC_NONE)
          {
            _emitX86RM(0xC7,
              dst.isRegType(REG_GPW),
              dst.isRegType(REG_GPQ),
              0,
              dst,
              0);
            immSize = 4;
          }
          else
          {
            _emitX86Inl((dst.size() == 1 ? 0xB0 : 0xB8),
              dst.isRegType(REG_GPW),
              dst.isRegType(REG_GPQ),
              ((Register) dst).code());
          }

          _emitImmediate(isrc, immSize);
          return;
        }

        // Mem <- Reg
        case (OP_MEM << 4) | OP_REG:
        {
          assert(src.isRegType(REG_GPB) || src.isRegType(REG_GPW) ||
                        src.isRegType(REG_GPD) || src.isRegType(REG_GPQ));

          _emitX86RM(0x88 + intValue(!src.isRegType(REG_GPB)),
            src.isRegType(REG_GPW),
            src.isRegType(REG_GPQ),
            ((Register) src).code(),
            dst,
            0);
          return;
        }

        // Mem <- Imm
        case (OP_MEM << 4) | OP_IMM:
        {
          int immSize = dst.size() <= 4 ? dst.size() : 4;

          _emitX86RM(0xC6 + intValue(dst.size() != 1),
            dst.size() == 2,
            dst.size() == 8,
            0,
            dst,
            immSize);
          _emitImmediate((Immediate) src,
            immSize);
          return;
        }
      }

      break;
    }

    case I_MOV_PTR:
    {
      if ((o1.isReg() && o2.isImm()) || (o1.isImm() && o2.isReg()))
      {
        boolean reverse = o1.op() == OP_REG;
        int opCode = !reverse ? 0xA0 : 0xA2;
        final Register reg = (Register)(!reverse ? o1 : o2);
        final Immediate imm = (Immediate)(!reverse ? o2 : o1);

        if (reg.index() != 0) throw new IllegalStateException("reg.index() != 0");

        if (reg.isRegType(REG_GPW)) _emitByte(0x66);

        if (is64()) {
            _emitRexR(reg.size() == 8, 0, 0);
        }

        _emitByte(opCode + intValue(reg.size() != 1));
        _emitImmediate(imm, is64() ? 8 : 4);
        return;
      }

      break;
    }

    case I_MOVSX_MOVZX:
    {
      if (o1.isReg() && o2.isRegMem())
      {
        final Register dst = (Register)(o1);
        final Operand src = (o2);

        if (dst.isRegType(REG_GPB)) throw new IllegalArgumentException("not gpb");
        if (src.size() != 1 && src.size() != 2) throw new IllegalArgumentException("src.size !=1 && src.size != 2");
        if (src.size() == 2 && dst.isRegType(REG_GPW)) throw new IllegalArgumentException("not gpw");

        _emitX86RM(id.opCode1 + intValue(src.size() != 1),
          dst.isRegType(REG_GPW),
          dst.isRegType(REG_GPQ),
          dst.code(),
          src,
          0);
        return;
      }

      break;
    }

    case I_MOVSXD:
    {
        if (!is64()) {
            throw new IllegalStateException("illegal instruction");
        }
      if (o1.isReg() && o2.isRegMem())
      {
        final Register dst = (Register)(o1);
        final Operand src = (o2);
        _emitX86RM(0x00000063,
          false,
          1, dst.code(), src,
          0);
        return;
      }

      break;
    }


    case I_PUSH:
    {
      // This section is only for immediates, memory/register operands are handled in I_POP.
      if (o1.isImm())
      {
        final Immediate imm = (Immediate)(o1);

        if (isInt8(imm.value()) && imm.relocMode() == RELOC_NONE)
        {
          _emitByte(0x6A);
          _emitImmediate(imm, 1);
        }
        else
        {
          _emitByte(0x68);
          _emitImmediate(imm, 4);
        }
        return;
      }

      // ... goto I_POP ...
    }

    case I_POP:
    {
      if (o1.isReg())
      {
        assert(o1.isRegType(REG_GPW) || o1.isRegType(is64() ? REG_GPQ : REG_GPD));
        _emitX86Inl(id.opCode1, o1.isRegType(REG_GPW), 0, ((Register) o1).code());
        return;
      }

      if (o1.isMem())
      {
        _emitX86RM(id.opCode2, o1.size() == 2, 0, id.opCodeR, (o1), 0);
        return;
      }

      break;
    }

    case I_R_RM:
    {
      if (o1.isReg() && o2.isRegMem())
      {
        final Register dst = (Register)(o1);
        assert(dst.type() != REG_GPB);
        final Operand src = (o2);

        _emitX86RM(id.opCode1,
          dst.type() == REG_GPW,
          dst.type() == REG_GPQ, dst.code(), src,
          0);
        return;
      }

      break;
    }

    case I_RM_B:
    {
      if (o1.isRegMem())
      {
        final Operand op = (o1);
        _emitX86RM(id.opCode1, false, false, 0, op, 0);
        return;
      }

      break;
    }

    case I_RM:
    {
      if (o1.isRegMem())
      {
        final Operand op = (o1);
        _emitX86RM(id.opCode1 + intValue(op.size() != 1),
          op.size() == 2,
          op.size() == 8, id.opCodeR, op,
          0);
        return;
      }

      break;
    }

    case I_RM_R:
    {
      if (o1.isRegMem() && o2.isReg())
      {
        final Operand dst = (o1);
        final Register src = (Register)(o2);
        _emitX86RM(id.opCode1 + intValue(src.type() != REG_GPB),
          src.type() == REG_GPW,
          src.type() == REG_GPQ, src.code(), dst,
          0);
        return;
      }

      break;
    }

    case I_RET:
    {
      if (o1.isNone())
      {
        _emitByte(0xC3);
        return;
      }
      else if (o1.isImm())
      {
        final Immediate imm = (Immediate)(o1);
        assert(isUInt16(imm.value()));

        if (imm.value() == 0 && imm.relocMode() == RELOC_NONE)
        {
          _emitByte(0xC3);
        }
        else
        {
          _emitByte(0xC2);
          _emitImmediate(imm, 2);
        }
        return;
      }

      break;
    }

    case I_ROT:
    {
      if (o1.isRegMem() && (o2.isRegCode(REG_CL) || o2.isImm()))
      {
        // generate opcode. For these operations is base 0xC0 or 0xD0.
        boolean useImm8 = (o2.isImm() &&
                       (((Immediate) o2).value() != 1 ||
                        ((Immediate) o2).relocMode() != RELOC_NONE));
        int opCode = useImm8 ? 0xC0 : 0xD0;

        // size and operand type modifies the opcode
        if (o1.size() != 1) opCode |= 0x01;
        if (o2.op() == OP_REG) opCode |= 0x02;

        _emitX86RM(opCode,
          o1.size() == 2,
          o1.size() == 8,
          id.opCodeR, (o1),
          intValue(useImm8));
        if (useImm8)
          _emitImmediate((Immediate)(o2), 1);
        return;
      }

      break;
    }

    case I_SHLD_SHRD:
    {
      if (o1.isRegMem() && o2.isReg() && (o3.isImm() || (o3.isReg() && o3.isRegCode(REG_CL))))
      {
        final Operand dst = (o1);
        final Register src1 = (Register)(o2);
        final Operand src2 = (o3);

        assert(dst.size() == src1.size());

        _emitX86RM(id.opCode1 + intValue(src2.isReg()),
          src1.isRegType(REG_GPW),
          src1.isRegType(REG_GPQ),
          src1.code(), dst,
          intValue(src2.isImm()));
        if (src2.isImm())
          _emitImmediate((Immediate)(src2), 1);
        return;
      }

      break;
    }

    case I_TEST:
    {
      if (o1.isRegMem() && o2.isReg())
      {
        assert(o1.size() == o2.size());
        _emitX86RM(0x84 + intValue(o2.size() != 1),
          o2.size() == 2, o2.size() == 8,
          ((BaseReg) o2).code(),
          (o1),
          0);
        return;
      }

      if (o1.isRegIndex(0) && o2.isImm())
      {
        int immSize = o1.size() <= 4 ? o1.size() : 4;

        if (o1.size() == 2) _emitByte(0x66); // 16 bit

        if (is64()) {
            _emitRexRM(o1.size() == 8, 0, (o1));
        }

        _emitByte(0xA8 + intValue(o1.size() != 1));
        _emitImmediate((Immediate)(o2), immSize);
        return;
      }

      if (o1.isRegMem() && o2.isImm())
      {
        int immSize = o1.size() <= 4 ? o1.size() : 4;

        if (o1.size() == 2) _emitByte(0x66); // 16 bit
        _emitSegmentPrefix((o1)); // segment prefix

        if (is64()) _emitRexRM(o1.size() == 8, 0, (o1));

        _emitByte(0xF6 + intValue(o1.size() != 1));
        _emitModRM(0, (o1), immSize);
        _emitImmediate((Immediate)(o2), immSize);
        return;
      }

      break;
    }

    case I_XCHG:
    {
      if (o1.isRegMem() && o2.isReg())
      {
        final Operand dst = (o1);
        final Register src = (Register)(o2);

        if (src.isRegType(REG_GPW)) _emitByte(0x66); // 16 bit
        _emitSegmentPrefix(dst); // segment prefix

        if (is64()) _emitRexRM(src.isRegType(REG_GPQ), src.code(), dst);

        // Special opcode for index 0 registers (AX, EAX, RAX vs register)
        if ((dst.op() == OP_REG && dst.size() > 1) &&
            (((Register) dst).code() == 0 ||
             ((Register) src).code() == 0))
        {
          int index = ((Register) dst).code() | src.code();
          _emitByte((byte) (0x90 + index));
          return;
        }

        _emitByte(0x86 + intValue(!src.isRegType(REG_GPB)));
        _emitModRM(src.code(), dst, 0);
        return;
      }

      break;
    }

    case I_MOVBE:
    {
      if (o1.isReg() && o2.isMem())
      {
        _emitX86RM(0x000F38F0,
          o1.isRegType(REG_GPW),
          o1.isRegType(REG_GPQ),
          ((Register) o1).code(),
          (Mem)(o2),
          0);
        return;
      }

      if (o1.isMem() && o2.isReg())
      {
        _emitX86RM(0x000F38F1,
          o2.isRegType(REG_GPW),
          o2.isRegType(REG_GPQ),
          ((Register) o2).code(),
          (Mem)(o1),
          0);
        return;
      }

      break;
    }

    case I_X87_FPU:
    {
      if (o1.isRegType(REG_X87))
      {
        int i1 = ((X87Register) o1).index();
        int i2 = 0;

        if (code != INST_FCOM && code != INST_FCOMP)
        {
          if (!o2.isRegType(REG_X87)) throw new IllegalArgumentException("not x87 reg");
          i2 = ((X87Register) o2).index();
        }
        else if (i1 != 0 && i2 != 0)
        {
          throw new IllegalArgumentException("illegal instruction");
        }

        _emitByte(i1 == 0
          ? ((id.opCode1 & 0xFF000000) >> 24)
          : ((id.opCode1 & 0x00FF0000) >> 16));
        _emitByte(i1 == 0
          ? ((id.opCode1 & 0x0000FF00) >>  8) + i2
          : ((id.opCode1 & 0x000000FF)      ) + i1);
        return;
      }

      if (o1.isMem() && (o1.size() == 4 || o1.size() == 8) && o2.isNone())
      {
        final Mem m = (Mem)(o1);

        // segment prefix
        _emitSegmentPrefix(m);

        _emitByte(o1.size() == 4
          ? ((id.opCode1 & 0xFF000000) >> 24)
          : ((id.opCode1 & 0x00FF0000) >> 16));
        _emitModM(id.opCodeR, m, 0);
        return;
      }

      break;
    }

    case I_X87_STI:
    {
      if (o1.isRegType(REG_X87))
      {
        int i = ((X87Register) o1).index();
        _emitByte(((id.opCode1 & 0x0000FF00) >> 8));
        _emitByte(((id.opCode1 & 0x000000FF) + i));
        return;
      }
      break;
    }

    case I_X87_FSTSW:
    {
      if (o1.isReg() &&
          ((BaseReg) o1).type() <= REG_GPQ &&
          ((BaseReg) o1).index() == 0)
      {
        _emitOpCode(id.opCode2);
        return;
      }

      if (o1.isMem())
      {
        _emitX86RM(id.opCode1, false, 0, id.opCodeR, (Mem)(o1), 0);
        return;
      }

      break;
    }

    case I_X87_MEM_STI:
    {
      if (o1.isRegType(REG_X87))
      {
        _emitByte(((id.opCode2 & 0xFF000000) >> 24));
        _emitByte(((id.opCode2 & 0x00FF0000) >> 16) +
          ((X87Register) o1).index());
        return;
      }

      // ... fall through to I_X87_MEM ...
    }

    case I_X87_MEM:
    {
      if (!o1.isMem()) throw new IllegalArgumentException("not x87 mem");
      final Mem m = (Mem)(o1);

      int opCode = 0x00, mod = 0;

      if (o1.size() == 2 && (id.o1Flags & O_FM_2) != 0)
      {
        opCode = ((id.opCode1 & 0xFF000000) >> 24);
        mod    = id.opCodeR;
      }
      if (o1.size() == 4 && (id.o1Flags & O_FM_4) != 0)
      {
        opCode = ((id.opCode1 & 0x00FF0000) >> 16);
        mod    = id.opCodeR;
      }
      if (o1.size() == 8 && (id.o1Flags & O_FM_8) != 0)
      {
        opCode = ((id.opCode1 & 0x0000FF00) >>  8);
        mod    = ((id.opCode1 & 0x000000FF)      );
      }

      if (opCode != 0)
      {
        _emitSegmentPrefix(m);
        _emitByte(opCode);
        _emitModM(mod, m, 0);
        return;
      }

      break;
    }

    case I_MMU_MOV:
    {
      assert(id.o1Flags != 0);
      assert(id.o2Flags != 0);

      // Check parameters (X)MM|GP32_64 <- (X)MM|GP32_64|Mem|Imm
      if ((o1.isMem()            && (id.o1Flags & O_MEM) == 0) ||
          (o1.isRegType(REG_MM ) && (id.o1Flags & O_MM ) == 0) ||
          (o1.isRegType(REG_XMM) && (id.o1Flags & O_XMM) == 0) ||
          (o1.isRegType(REG_GPD) && (id.o1Flags & O_G32) == 0) ||
          (o1.isRegType(REG_GPQ) && (id.o1Flags & O_G64) == 0) ||
          (o2.isRegType(REG_MM ) && (id.o2Flags & O_MM ) == 0) ||
          (o2.isRegType(REG_XMM) && (id.o2Flags & O_XMM) == 0) ||
          (o2.isRegType(REG_GPD) && (id.o2Flags & O_G32) == 0) ||
          (o2.isRegType(REG_GPQ) && (id.o2Flags & O_G64) == 0) ||
          (o2.isMem()            && (id.o2Flags & O_MEM) == 0) )
      {
        throw new IllegalArgumentException("illegal instruction");
      }

      // Illegal
      if (o1.isMem() && o2.isMem()) throw new IllegalArgumentException("illegal instruction");

      int rexw = ((id.o1Flags|id.o2Flags) & O_NOREX) != 0
        ? 0
        : intValue(o1.isRegType(REG_GPQ) || o1.isRegType(REG_GPQ));

      // (X)MM|Reg <- (X)MM|Reg
      if (o1.isReg() && o2.isReg())
      {
        _emitMmu(id.opCode1, rexw,
          ((BaseReg) o1).code(),
          (BaseReg)(o2),
          0);
        return;
      }

      // (X)MM|Reg <- Mem
      if (o1.isReg() && o2.isMem())
      {
        _emitMmu(id.opCode1, rexw,
          ((BaseReg) o1).code(),
          (Mem)(o2),
          0);
        return;
      }

      // Mem <- (X)MM|Reg
      if (o1.isMem() && o2.isReg())
      {
        _emitMmu(id.opCode2, rexw,
          ((BaseReg) o2).code(),
          (Mem)(o1),
          0);
        return;
      }

      break;
    }

    case I_MMU_MOVD:
    {
      if ((o1.isRegType(REG_MM) || o1.isRegType(REG_XMM)) && (o2.isRegType(REG_GPD) || o2.isMem()))
      {
        _emitMmu(o1.isRegType(REG_XMM) ? 0x66000F6E : 0x00000F6E, 0,
          ((BaseReg) o1).code(),
          (o2),
          0);
        return;
      }

      if ((o1.isRegType(REG_GPD) || o1.isMem()) && (o2.isRegType(REG_MM) || o2.isRegType(REG_XMM)))
      {
        _emitMmu(o2.isRegType(REG_XMM) ? 0x66000F7E : 0x00000F7E, 0,
          ((BaseReg) o2).code(),
          (o1),
          0);
        return;
      }

      break;
    }

    case I_MMU_MOVQ:
    {
      if (o1.isRegType(REG_MM) && o2.isRegType(REG_MM))
      {
        _emitMmu(0x00000F6F, 0,
          ((MMRegister) o1).code(),
          (MMRegister)(o2),
          0);
        return;
      }

      if (o1.isRegType(REG_XMM) && o2.isRegType(REG_XMM))
      {
        _emitMmu(0xF3000F7E, 0,
          ((XMMRegister) o1).code(),
          (XMMRegister)(o2),
          0);
        return;
      }

      // Convenience - movdq2q
      if (o1.isRegType(REG_MM) && o2.isRegType(REG_XMM))
      {
        _emitMmu(0xF2000FD6, 0,
          ((MMRegister) o1).code(),
          (XMMRegister)(o2),
          0);
        return;
      }

      // Convenience - movq2dq
      if (o1.isRegType(REG_XMM) && o2.isRegType(REG_MM))
      {
        _emitMmu(0xF3000FD6, 0,
          ((XMMRegister) o1).code(),
          (MMRegister)(o2),
          0);
        return;
      }

      if (o1.isRegType(REG_MM) && o2.isMem())
      {
        _emitMmu(0x00000F6F, 0,
          ((MMRegister) o1).code(),
          (Mem)(o2),
          0);
        return;
      }

      if (o1.isRegType(REG_XMM) && o2.isMem())
      {
        _emitMmu(0xF3000F7E, 0,
          ((XMMRegister) o1).code(),
          (Mem)(o2),
          0);
        return;
      }

      if (o1.isMem() && o2.isRegType(REG_MM))
      {
        _emitMmu(0x00000F7F, 0,
          ((MMRegister) o2).code(),
          (Mem)(o1),
          0);
        return;
      }

      if (o1.isMem() && o2.isRegType(REG_XMM))
      {
        _emitMmu(0x66000FD6, 0,
          ((XMMRegister) o2).code(),
          (Mem)(o1),
          0);
        return;
      }


        if (is64()) {
          if ((o1.isRegType(REG_MM) || o1.isRegType(REG_XMM)) && (o2.isRegType(REG_GPQ) || o2.isMem()))
          {
            _emitMmu(o1.isRegType(REG_XMM) ? 0x66000F6E : 0x00000F6E, 1,
              ((BaseReg) o1).code(),
              (o2),
              0);
            return;
          }

          if ((o1.isRegType(REG_GPQ) || o1.isMem()) && (o2.isRegType(REG_MM) || o2.isRegType(REG_XMM)))
          {
            _emitMmu(o2.isRegType(REG_XMM) ? 0x66000F7E : 0x00000F7E, 1,
              ((BaseReg) o2).code(),
              (o1),
              0);
            return;
          }
        }

      break;
    }

    case I_MMU_PREFETCH:
    {
      if (o1.isMem() && o2.isImm())
      {
        final Mem mem = (Mem)(o1);
        final Immediate hint = (Immediate)(o2);

        _emitMmu(0x00000F18, 0, (int) hint.value(), mem, 0);
        return;
      }

      break;
    }

    case I_MMU_PEXTR:
    {
      if (!(o1.isRegMem() &&
           (o2.isRegType(REG_XMM) || (code == INST_PEXTRW && o2.isRegType(REG_MM))) &&
            o3.isImm()))
      {
        throw new IllegalStateException("illegal instruction");
      }

      int opCode = id.opCode1;
      boolean isGpdGpq = o1.isRegType(REG_GPD) || o1.isRegType(REG_GPQ);

      if (code == INST_PEXTRB && (o1.size() != 0 && o1.size() != 1) && !isGpdGpq) throw new IllegalStateException("illegal instruction");
      if (code == INST_PEXTRW && (o1.size() != 0 && o1.size() != 2) && !isGpdGpq) throw new IllegalStateException("illegal instruction");
      if (code == INST_PEXTRD && (o1.size() != 0 && o1.size() != 4) && !isGpdGpq) throw new IllegalStateException("illegal instruction");
      if (code == INST_PEXTRQ && (o1.size() != 0 && o1.size() != 8) && !isGpdGpq) throw new IllegalStateException("illegal instruction");

      if (o2.isRegType(REG_XMM)) opCode |= 0x66000000;

      if (o1.isReg())
      {
        _emitMmu(opCode, id.opCodeR | intValue(o1.isRegType(REG_GPQ)),
          ((BaseReg) o2).code(),
          (BaseReg)(o1), 1);
        _emitImmediate(
          (Immediate)(o3), 1);
        return;
      }

      if (o1.isMem())
      {
        _emitMmu(opCode, id.opCodeR,
          ((BaseReg) o2).code(),
          (Mem)(o1), 1);
        _emitImmediate(
          (Immediate)(o3), 1);
        return;
      }

      break;
    }

    case I_MMU_RMI:
    {
      assert(id.o1Flags != 0);
      assert(id.o2Flags != 0);

      // Check parameters (X)MM|GP32_64 <- (X)MM|GP32_64|Mem|Imm
      if (!o1.isReg() ||
          (o1.isRegType(REG_MM ) && (id.o1Flags & O_MM ) == 0) ||
          (o1.isRegType(REG_XMM) && (id.o1Flags & O_XMM) == 0) ||
          (o1.isRegType(REG_GPD) && (id.o1Flags & O_G32) == 0) ||
          (o1.isRegType(REG_GPQ) && (id.o1Flags & O_G64) == 0) ||
          (o2.isRegType(REG_MM ) && (id.o2Flags & O_MM ) == 0) ||
          (o2.isRegType(REG_XMM) && (id.o2Flags & O_XMM) == 0) ||
          (o2.isRegType(REG_GPD) && (id.o2Flags & O_G32) == 0) ||
          (o2.isRegType(REG_GPQ) && (id.o2Flags & O_G64) == 0) ||
          (o2.isMem()            && (id.o2Flags & O_MEM) == 0) ||
          (o2.isImm()            && (id.o2Flags & O_IMM) == 0))
      {
        throw new IllegalStateException("illegal instruction");
      }

      int prefix =
        ((id.o1Flags & O_MM_XMM) == O_MM_XMM && o1.isRegType(REG_XMM)) ||
        ((id.o2Flags & O_MM_XMM) == O_MM_XMM && o2.isRegType(REG_XMM))
          ? 0x66000000
          : 0x00000000;
      int rexw = ((id.o1Flags|id.o2Flags) & O_NOREX) != 0
        ? 0
        : intValue(o1.isRegType(REG_GPQ) || o1.isRegType(REG_GPQ));

      // (X)MM <- (X)MM (opcode1)
      if (o2.isReg())
      {
        if ((id.o2Flags & (O_MM_XMM | O_G32_64)) == 0) throw new IllegalStateException("illegal instruction");
        _emitMmu(id.opCode1 | prefix, rexw,
          ((BaseReg) o1).code(),
          (BaseReg)(o2), 0);
        return;
      }
      // (X)MM <- Mem (opcode1)
      if (o2.isMem())
      {
        if ((id.o2Flags & O_MEM) == 0) throw new IllegalStateException("illegal instruction");
        _emitMmu(id.opCode1 | prefix, rexw,
          ((BaseReg) o1).code(),
          (Mem)(o2), 0);
        return;
      }
      // (X)MM <- Imm (opcode2+opcodeR)
      if (o2.isImm())
      {
        if ((id.o2Flags & O_IMM) == 0) throw new IllegalStateException("illegal instruction");
        _emitMmu(id.opCode2 | prefix, rexw,
          id.opCodeR,
          (BaseReg)(o1), 1);
        _emitImmediate(
          (Immediate)(o2), 1);
        return;
      }

      break;
    }

    case I_MMU_RM_IMM8:
    {
      assert(id.o1Flags != 0);
      assert(id.o2Flags != 0);

      // Check parameters (X)MM|GP32_64 <- (X)MM|GP32_64|Mem|Imm
      if (!o1.isReg() ||
          (o1.isRegType(REG_MM ) && (id.o1Flags & O_MM ) == 0) ||
          (o1.isRegType(REG_XMM) && (id.o1Flags & O_XMM) == 0) ||
          (o1.isRegType(REG_GPD) && (id.o1Flags & O_G32) == 0) ||
          (o1.isRegType(REG_GPQ) && (id.o1Flags & O_G64) == 0) ||
          (o2.isRegType(REG_MM ) && (id.o2Flags & O_MM ) == 0) ||
          (o2.isRegType(REG_XMM) && (id.o2Flags & O_XMM) == 0) ||
          (o2.isRegType(REG_GPD) && (id.o2Flags & O_G32) == 0) ||
          (o2.isRegType(REG_GPQ) && (id.o2Flags & O_G64) == 0) ||
          (o2.isMem()            && (id.o2Flags & O_MEM) == 0) ||
          !o3.isImm())
      {
        throw new IllegalStateException("illegal instruction");
      }

      int prefix =
        ((id.o1Flags & O_MM_XMM) == O_MM_XMM && o1.isRegType(REG_XMM)) ||
        ((id.o2Flags & O_MM_XMM) == O_MM_XMM && o2.isRegType(REG_XMM))
          ? 0x66000000
          : 0x00000000;
      int rexw = ((id.o1Flags|id.o2Flags) & O_NOREX) != 0
        ? 0
        : intValue(o1.isRegType(REG_GPQ) || o1.isRegType(REG_GPQ));

      // (X)MM <- (X)MM (opcode1)
      if (o2.isReg())
      {
        if ((id.o2Flags & (O_MM_XMM | O_G32_64)) == 0) throw new IllegalStateException("illegal instruction");
        _emitMmu(id.opCode1 | prefix, rexw,
          ((BaseReg) o1).code(),
          (BaseReg)(o2), 1);
        _emitImmediate((Immediate)(o3), 1);
        return;
      }
      // (X)MM <- Mem (opcode1)
      if (o2.isMem())
      {
        if ((id.o2Flags & O_MEM) == 0) throw new IllegalStateException("illegal instruction");
        _emitMmu(id.opCode1 | prefix, rexw,
          ((BaseReg) o1).code(),
          (Mem)(o2), 1);
        _emitImmediate((Immediate)(o3), 1);
        return;
      }

      break;
    }

    case I_MMU_RM_3DNOW:
    {
      if (o1.isRegType(REG_MM) && (o2.isRegType(REG_MM) || o2.isMem()))
      {
        _emitMmu(id.opCode1, 0,
          ((BaseReg) o1).code(),
          (Mem)(o2), 1);
        _emitByte(id.opCode2);
        return;
      }

      break;
    }
  }
    }

    void _emitFpu(int opCode) {
        _emitOpCode(opCode);
    }

    void _emitFpuSTI(int opCode, int sti) {
        // illegal stack offset
        assert (0 <= sti && sti < 8);
        _emitOpCode(opCode + sti);
    }

    void _emitFpuMEM(int opCode, int opReg, Mem mem) {
        // segment prefix
        _emitSegmentPrefix(mem);

        // instruction prefix
        if ((opCode & 0xFF000000) != 0) {
            _emitByte(((opCode & 0xFF000000) >> 24));
        }

        // rex prefix
        if (is64()) {
            _emitRexRM(0, opReg, mem);
        }

        // instruction opcodes
        if ((opCode & 0x00FF0000) != 0) {
            _emitByte(((opCode & 0x00FF0000) >> 16));
        }
        if ((opCode & 0x0000FF00) != 0) {
            _emitByte(((opCode & 0x0000FF00) >> 8));
        }

        _emitByte(((opCode & 0x000000FF)));
        _emitModM(opReg, mem, 0);
    }

    void _emitMmu(int opCode, int rexw, int opReg,
            Operand src, int immSize) {
        // Segment prefix.
        _emitSegmentPrefix(src);

        // Instruction prefix.
        if ((opCode & 0xFF000000) != 0) {
            _emitByte(((opCode & 0xFF000000) >> 24));
        }

        // Rex prefix
        if (is64()) {
            _emitRexRM(rexw, opReg, src);
        }

        // Instruction opcodes.
        if ((opCode & 0x00FF0000) != 0) {
            _emitByte(((opCode & 0x00FF0000) >> 16));
        }

        // No checking, MMX/SSE instructions have always two opcodes or more.
        _emitByte(((opCode & 0x0000FF00) >> 8));
        _emitByte(((opCode & 0x000000FF)));

        if (src.isReg()) {
            _emitModR(opReg, ((BaseReg) src).code());
        } else {
            _emitModM(opReg, (Mem) src, immSize);
        }
    }

    LinkData _emitDisplacement(Label label, long inlinedDisplacement, int size) {
        assert (!label.isBound());
        assert (size == 1 || size == 4);

        // Chain with label.
        LinkData link = new LinkData(offset(), inlinedDisplacement, -1);

        label.link(link);

        // Emit dummy DWORD.
        if (size == 1) {
            _emitByte(0x01);
        } else // if (size == 4)
        {
            _emitDWord(0x04040404);
        }

        return link;
    }

    void _emitJmpOrCallReloc(InstructionGroup instruction, long target) {


        if (is64()) {
            // If we are compiling in 64-bit mode, we can use trampoline if relative jump
            // is not possible.
            _trampolineSize += TrampolineWriter.TRAMPOLINE_SIZE;
        }

        RelocData rd = new RelocData(RelocData.Type.ABSOLUTE_TO_RELATIVE_TRAMPOLINE, 4, offset(), target);
        _relocData.add(rd);

        // Emit dummy 32-bit integer (will be overwritten by relocCode()).
        _emitInt32(0);
    }

    public void relocCode(ByteBuffer buffer, long address) {
        // Copy code to virtual memory (this is a given _dst pointer).
        int csize = codeSize();

        // We are copying exactly size of generated code. Extra code for trampolines
        // is generated on-the-fly by relocator (this code not exists at now).
        _buffer.copyTo(buffer);

        // Relocate recorded locations.
        for (RelocData r : _relocData) {

            long val;

            // Whether to use trampoline, can be only used if relocation type is
            // ABSOLUTE_TO_RELATIVE_TRAMPOLINE.
            boolean useTrampoline = false;


            // Be sure that reloc data structure is correct.
            assert ((r.offset + r.size) <= csize);

            switch (r.type) {
                case ABSOLUTE_TO_ABSOLUTE:
                    val = r.destination;
                    break;

                case RELATIVE_TO_ABSOLUTE:
                    val = address + r.destination;
                    break;

                case ABSOLUTE_TO_RELATIVE:
                case ABSOLUTE_TO_RELATIVE_TRAMPOLINE:
                    val = r.destination - (address + r.offset + 4);

                    if (is64() && r.type == RelocData.Type.ABSOLUTE_TO_RELATIVE_TRAMPOLINE && !isInt32(val)) {
                        val = (long) buffer.position() - (r.offset + 4);
                        useTrampoline = true;
                    }

                    break;

                default:
                    throw new IllegalStateException("invalid relocation type");
            }

            switch (r.size) {
                case 4:
                    buffer.putInt(r.offset, (int) val);
                    break;

                case 8:
                    buffer.putLong(r.offset, val);
                    break;

                default:
                    throw new IllegalStateException("invalid relocation size");
            }

            if (is64() && useTrampoline) {
                if (_logger != null) {
                    _logger.log(String.format("; Trampoline from %x -> %x\n", address + r.offset, r.destination));
                }

                TrampolineWriter.writeTrampoline(buffer, r.destination);
            }
        }
    }
    // NOPs optimized for Intel:
    //   Intel 64 and IA-32 Architectures Software Developer's Manual
    //   - Volume 2B
    //   - Instruction Set Reference N-Z
    //     - NOP

    // NOPs optimized for AMD:
    //   Software Optimization Guide for AMD Family 10h Processors (Quad-Core)
    //   - 4.13 - Code Padding with Operand-Size Override and Multibyte NOP
    // Intel and AMD
    private static final int nop1[] = { 0x90 };
    private static final int nop2[] = { 0x66, 0x90 };
    private static final int nop3[] = { 0x0F, 0x1F, 0x00 };
    private static final int nop4[] = { 0x0F, 0x1F, 0x40, 0x00 };
    private static final int nop5[] = { 0x0F, 0x1F, 0x44, 0x00, 0x00 };
    private static final int nop6[] = { 0x66, 0x0F, 0x1F, 0x44, 0x00, 0x00 };
    private static final int nop7[] = { 0x0F, 0x1F, 0x80, 0x00, 0x00, 0x00, 0x00 };
    private static final int nop8[] = { 0x0F, 0x1F, 0x84, 0x00, 0x00, 0x00, 0x00, 0x00 };
    private static final int nop9[] = { 0x66, 0x0F, 0x1F, 0x84, 0x00, 0x00, 0x00, 0x00, 0x00 };

    // AMD
    private static final int nop10[] = { 0x66, 0x66, 0x0F, 0x1F, 0x84, 0x00, 0x00, 0x00, 0x00, 0x00 };
    private static final int nop11[] = { 0x66, 0x66, 0x66, 0x0F, 0x1F, 0x84, 0x00, 0x00, 0x00, 0x00, 0x00 };

    public void align(long m) {
        if (_logger != null) _logger.logAlign(m);

        if (m < 1) return;

        if (m > 64) {
            assert (m <= 64);
            return;
        }

        int i = (int) (m - (offset() % m));
        if (i == m) return;

        if ((_properties & (1 << PROPERTY_OPTIMIZE_ALIGN)) != 0) {
            int n;

            if (cpuInfo.vendor == CpuInfo.Vendor.INTEL &&
               ((cpuInfo.family & 0x0F) == 6 ||
                (cpuInfo.family & 0x0F) == 15)) {
                do {
                    int[] p;
                    switch (i) {
                        case  1: p = nop1; n = 1; break;
                        case  2: p = nop2; n = 2; break;
                        case  3: p = nop3; n = 3; break;
                        case  4: p = nop4; n = 4; break;
                        case  5: p = nop5; n = 5; break;
                        case  6: p = nop6; n = 6; break;
                        case  7: p = nop7; n = 7; break;
                        case  8: p = nop8; n = 8; break;
                        default: p = nop9; n = 9; break;
                    }

                    i -= n;
                    for (int idx = 0; n > 0; ++idx, --n) {
                        _emitByte(p[idx]);
                    }
                } while (i > 0);

                return;
            }

            if (cpuInfo.vendor == CpuInfo.Vendor.AMD
                    && cpuInfo.family >= 0x0F) {
                do {
                    int[] p;
                    switch (i) {
                        case  1: p = nop1 ; n =  1; break;
                        case  2: p = nop2 ; n =  2; break;
                        case  3: p = nop3 ; n =  3; break;
                        case  4: p = nop4 ; n =  4; break;
                        case  5: p = nop5 ; n =  5; break;
                        case  6: p = nop6 ; n =  6; break;
                        case  7: p = nop7 ; n =  7; break;
                        case  8: p = nop8 ; n =  8; break;
                        case  9: p = nop9 ; n =  9; break;
                        case 10: p = nop10; n = 10; break;
                        default: p = nop11; n = 11; break;
                    }

                    i -= n;
                    for (int idx = 0; n > 0; ++idx, --n) {
                        _emitByte(p[idx]);
                    }
                } while (i > 0);

                return;
            }

            if (!is64()) {
                // legacy NOPs, 0x90 with 0x66 prefix.
                do {
                    switch (i) {
                        default: _emitByte(0x66); i--;
                        case  3: _emitByte(0x66); i--;
                        case  2: _emitByte(0x66); i--;
                        case  1: _emitByte(0x90); i--;
                    }
                } while (i > 0);
            }
        }

        // legacy NOPs, only 0x90
        // In 64-bit mode, we can't use 0x66 prefix
        while (i-- > 0) {
            _emitByte(0x90);
        }
    }

}
